diff --git a/.gitignore b/.gitignore new file mode 100644 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Ignore the following files +*~ +*.[oa] +*.diff +*.kate-swp +*.kdev4 +.kdev_include_paths +*.kdevelop.pcs +*.moc +*.moc.cpp +*.orig +*.user +.*.swp +.swp.* +Doxyfile +Makefile +avail +random_seed +/build*/ +CMakeLists.txt.user* +*.unc-backup* diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 3.0) project(plasma-workspace) -set(PROJECT_VERSION "5.16.80") +set(PROJECT_VERSION "5.17.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.12.0") -set(KF5_MIN_VERSION "5.58.0") +set(KF5_MIN_VERSION "5.62.0") set(INSTALL_SDDM_THEME TRUE) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Widgets Quick QuickWidgets Concurrent Test Network) find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) @@ -21,12 +21,14 @@ include(FeatureSummary) include(ECMOptionalAddSubdirectory) include(ECMQtDeclareLoggingCategory) +include(ECMQueryQmake) include(KDEPackageAppTemplates) +include(KDEClangFormat) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS - Plasma DocTools Runner JsEmbed NotifyConfig Su NewStuff Wallet + Plasma DocTools Runner NotifyConfig Su NewStuff Wallet IdleTime Declarative I18n KCMUtils TextWidgets KDELibs4Support Crash GlobalAccel - DBusAddons Wayland CoreAddons) + DBusAddons Wayland CoreAddons People ActivitiesStats) find_package(KDED CONFIG REQUIRED) find_package(KF5NetworkManagerQt ${KF5_MIN_VERSION}) @@ -41,7 +43,14 @@ TYPE RUNTIME ) +find_package(KF5QuickCharts ${KF5_MIN_VERSION} CONFIG) +set_package_properties(KF5QuickCharts PROPERTIES + DESCRIPTION "Used for rendering charts" + TYPE RUNTIME +) + # WARNING PlasmaQuick provides unversioned CMake config +find_package(KUserFeedback) find_package(KF5 REQUIRED COMPONENTS PlasmaQuick) find_package(KF5 REQUIRED COMPONENTS SysGuard) find_package(KF5 REQUIRED COMPONENTS Package) @@ -55,6 +64,7 @@ find_package(KF5TextEditor) find_package(KWinDBusInterface CONFIG REQUIRED) +find_package(KF5Screen CONFIG REQUIRED) find_package(KScreenLocker 5.13.80 REQUIRED) find_package(ScreenSaverDBusInterface CONFIG REQUIRED) find_package(KF5Holidays) @@ -75,13 +85,13 @@ find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Support for gzip compressed files and data streams" - URL "http://www.zlib.net" + URL "https://www.zlib.net" TYPE REQUIRED ) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" - URL "http://www.x.org" + URL "https://www.x.org" TYPE OPTIONAL PURPOSE "Required for building the X11 based workspace") @@ -105,14 +115,18 @@ TYPE OPTIONAL ) +if(${AppStreamQt_FOUND}) + set(HAVE_APPSTREAMQT true) +endif() include(ConfigureChecks.cmake) include_directories("${CMAKE_CURRENT_BINARY_DIR}") configure_file(config-workspace.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-workspace.h) configure_file(config-unix.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-unix.h ) configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h) +configure_file(config-appstream.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-appstream.h ) add_subdirectory(login-sessions) plasma_install_package(lookandfeel org.kde.breeze.desktop look-and-feel lookandfeel) @@ -132,6 +146,9 @@ add_definitions(-DQT_NO_URL_CAST_FROM_STRING) +# locate qdbus in the Qt path because not every distro makes a symlink at /usr/bin/qdbus +query_qmake(QtBinariesDir QT_INSTALL_BINS) + add_subdirectory(doc) add_subdirectory(libkworkspace) add_subdirectory(libdbusmenuqt) @@ -187,9 +204,9 @@ add_subdirectory(templates) -if (${ECM_VERSION} STRGREATER "5.58.0") - install(FILES plasma-workspace.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) -else() - install(FILES plasma-workspace.categories DESTINATION ${KDE_INSTALL_CONFDIR}) -endif() +install(FILES plasma-workspace.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) + +# add clang-format target for all our real source files +file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) +kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) diff --git a/applets/CMakeLists.txt b/applets/CMakeLists.txt --- a/applets/CMakeLists.txt +++ b/applets/CMakeLists.txt @@ -12,6 +12,8 @@ add_subdirectory(calendar) add_subdirectory(devicenotifier) add_subdirectory(digital-clock) +add_subdirectory(kicker) + plasma_install_package(clipboard org.kde.plasma.clipboard) if(NOT WIN32) diff --git a/applets/activitybar/metadata.desktop b/applets/activitybar/metadata.desktop --- a/applets/activitybar/metadata.desktop +++ b/applets/activitybar/metadata.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Activity Bar Name[ar]=شريط أنشطة +Name[ast]=Barra d'actividaes Name[be@latin]=Panel zaniatkaŭ Name[bg]=Лента за активност Name[bs]=Traka aktivnosti @@ -62,7 +63,7 @@ Name[sv]=Aktivitetsrad Name[ta]=Activity Bar Name[te]=క్రియాశీలత పట్టీ -Name[tg]=Панели фаъолиятӣ +Name[tg]=Навори фаъолият Name[th]=แถบกิจกรรม Name[tr]=Etkinlik Çubuğu Name[ug]=پائالىيەت بالدىقى @@ -91,7 +92,7 @@ Comment[fr]=Barre d'onglets permettant de changer d'activité Comment[fy]=Ljepper om tusken aktiviteiten te wikseljen Comment[ga]=Barra na gcluaisíní chun an ghníomhaíocht a mhalartú -Comment[gl]=Barra de lapelas para cambiar entre actividades +Comment[gl]=Barra de separadores para cambiar entre actividades Comment[gu]=ક્રિયાઓ બદલવા માટે ટેબ પટ્ટી Comment[he]=סרגל לשוניות למעבר בין פעילויות Comment[hi]=क्रिया स्विच करने के लिए टैबपट्टी @@ -106,7 +107,7 @@ Comment[km]=របារ​ផ្ទាំង​ដើម្បី​ប្ដូរ​សកម្មភាព Comment[kn]=ಚಟುವಟಿಕೆಗಳನ್ನು ಬದಲಾಯಿಸುವ ಟ್ಯಾಬ್ ಪಟ್ಟಿ Comment[ko]=활동 사이를 전환할 수 있는 탭 표시줄 -Comment[lt]=Kortelių juosta veiklų keitimui +Comment[lt]=Kortelių juosta, skirta perjungti veiklas Comment[lv]=Ciļnu josla aktivitāšu pārslēgšanai Comment[mai]=क्रियाकलाप स्विच करबाक लेल टैबपट्टी Comment[ml]=പ്രവര്‍ത്തനങ്ങള്‍ മാറ്റാന്‍ ടാബ്-ബാര്‍ @@ -129,7 +130,6 @@ Comment[sr@ijekavianlatin]=Traka jezičaka za prebacivanje aktivnosti Comment[sr@latin]=Traka jezičaka za prebacivanje aktivnosti Comment[sv]=Flikrad för att byta aktiviteter -Comment[tg]=Переключиться в командную строку Comment[th]=แถบแท็บเพื่อใช้สลับกิจกรรม Comment[tr]=Eylemleri seçmek için sekme çubuğu Comment[ug]=پائالىيەت ئالماشتۇرىدىغان بەتكۈچ بالداق diff --git a/applets/analog-clock/metadata.desktop b/applets/analog-clock/metadata.desktop --- a/applets/analog-clock/metadata.desktop +++ b/applets/analog-clock/metadata.desktop @@ -2,6 +2,7 @@ Name=Analog Clock Name[af]=Analooghorlosie Name[ar]=ساعة تناظرية +Name[ast]=Reló analóxicu Name[be]=Аналагавы гадзіннік Name[be@latin]=Analahavy hadzińnik Name[bg]=Аналогов часовник @@ -85,6 +86,7 @@ Name[zh_TW]=類比時鐘 Comment=A clock with hands Comment[ar]=ساعة بِعقارب +Comment[ast]=Un reló con aguyes Comment[bg]=Часовник със стрелки Comment[bs]=Sat sa kazaljkama Comment[ca]=Un rellotge amb agulles @@ -97,7 +99,7 @@ Comment[eo]=Horloĝo kun manoj Comment[es]=Un reloj con manecillas Comment[et]=Seieritega kell -Comment[eu]=Erloju orrazduna +Comment[eu]=Erloju orraztuna Comment[fi]=Viisarikello Comment[fr]=Une horloge à aiguilles Comment[fy]=In klok mei wizerplaat @@ -138,7 +140,6 @@ Comment[sr@ijekavianlatin]=Sat sa kazaljkama Comment[sr@latin]=Sat sa kazaljkama Comment[sv]=En klocka med visare -Comment[tg]=Соат бо ақрабакҳо Comment[th]=นาฬิกาแบบเข็ม Comment[tr]=İbreli bir saat Comment[ug]=تىللىق سائەت @@ -162,7 +163,7 @@ X-KDE-PluginInfo-Email=mart@kde.org X-KDE-PluginInfo-Name=org.kde.plasma.analogclock X-KDE-PluginInfo-Version=3.0 -X-KDE-PluginInfo-Website=http://userbase.kde.org/Plasma/Clocks +X-KDE-PluginInfo-Website=https://userbase.kde.org/Plasma/Clocks X-KDE-PluginInfo-Category=Date and Time X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL-2.0+ diff --git a/applets/appmenu/lib/appmenuapplet.cpp b/applets/appmenu/lib/appmenuapplet.cpp --- a/applets/appmenu/lib/appmenuapplet.cpp +++ b/applets/appmenu/lib/appmenuapplet.cpp @@ -55,7 +55,7 @@ another destroyedchanged and destroyed will be false. When this happens, if we are the only appmenu applet existing, the dbus interface will have to be registered again*/ - connect(this, &Applet::destroyedChanged, this, [this](bool destroyed) { + connect(this, &Applet::destroyedChanged, this, [](bool destroyed) { if (destroyed) { //if we were the last, unregister if (--s_refs == 0) { diff --git a/applets/appmenu/package/contents/ui/main.qml b/applets/appmenu/package/contents/ui/main.qml --- a/applets/appmenu/package/contents/ui/main.qml +++ b/applets/appmenu/package/contents/ui/main.qml @@ -18,7 +18,7 @@ */ import QtQuick 2.0 import QtQuick.Layouts 1.1 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.8 import org.kde.plasma.plasmoid 2.0 import org.kde.kquickcontrolsaddons 2.0 diff --git a/applets/appmenu/package/metadata.desktop b/applets/appmenu/package/metadata.desktop --- a/applets/appmenu/package/metadata.desktop +++ b/applets/appmenu/package/metadata.desktop @@ -1,14 +1,16 @@ [Desktop Entry] Name=Global Menu Name[ar]=القائمة العموميّة +Name[ast]=Menú global Name[ca]=Menú global Name[ca@valencia]=Menú global Name[cs]=Globální nabídka Name[da]=Global menu Name[de]=Globales Menü Name[el]=Καθολικό μενού Name[en_GB]=Global Menu Name[es]=Menú global +Name[et]=Globaalne menüü Name[eu]=Menu orokorra Name[fi]=Yleisvalikko Name[fr]=Menu global @@ -19,49 +21,53 @@ Name[it]=Menu globale Name[ko]=전역 메뉴 Name[lt]=Visuotinis meniu +Name[lv]=Globālā izvēlne Name[nl]=Globaal menu Name[nn]=Global meny Name[pa]=ਗਲੋਬਲ ਮੇਨੂ Name[pl]=Menu globalne Name[pt]=Menu Global Name[pt_BR]=Menu global Name[ru]=Меню приложения -Name[sk]=Globálne menu +Name[sk]=Globálna ponuka Name[sl]=Splošni meni Name[sr]=Глобални мени Name[sr@ijekavian]=Глобални мени Name[sr@ijekavianlatin]=Globalni meni Name[sr@latin]=Globalni meni Name[sv]=Global meny +Name[tg]=Феҳристи умумӣ Name[tr]=Genel Menü Name[uk]=Загальне меню Name[x-test]=xxGlobal Menuxx Name[zh_CN]=全局菜单 Name[zh_TW]=全域選單 Comment=Global menubar on top of the screen Comment[ar]=شريط القوائم العموميّ أعلى الشّاشة Comment[ca]=Barra de menús global a la part superior de la pantalla -Comment[ca@valencia]=Barra de menús global a la part superior de la pantalla +Comment[ca@valencia]=Barra de menús global en la part superior de la pantalla Comment[da]=Global menulinje øverst på skærmen Comment[de]=Globale Menüleiste oben auf dem Bildschirm Comment[el]=Καθολική μπάρα μενού στη κορυφή της οθόνης Comment[en_GB]=Global menubar on top of the screen Comment[es]=Barra de menú global en la parte superior de la pantalla +Comment[et]=Globaalne menüü töölaua ülaservas Comment[eu]=Menu-barra orokorra pantailaren gainaldean Comment[fi]=Työpöydänlaajuinen valikkorivi ruudun ylälaidassa Comment[fr]=Barre de menu globale en haut de l'écran Comment[gl]=Barra de menú global na parte superior da pantalla. Comment[hu]=Globális menüsáv a képernyő tetején Comment[id]=Bilah menu global berada di atas layar Comment[it]=Barra dei menu globale nella parte alta dello schermo Comment[ko]=화면 위에 전역 메뉴 표시줄 보이기 +Comment[lt]=Visuotinė meniu juosta ekrano viršuje Comment[nl]=Globale menubalk bovenaan het scherm Comment[nn]=Global menylinje øvst på skjermen Comment[pl]=Globalny pasek menu na górze ekranu Comment[pt]=Menu global no topo do ecrã Comment[pt_BR]=Barra de menu global no topo da tela Comment[ru]=Строка меню приложения на верхнем краю экрана -Comment[sk]=Globálna ponuka na vrchu obrazovky +Comment[sk]=Globálna ponuka navrchu obrazovky Comment[sl]=Splošna menijska vrstica na vrhu zaslona Comment[sr]=Глобална трака менија на врху екрана Comment[sr@ijekavian]=Глобална трака менија на врху екрана diff --git a/applets/appmenu/plugin/appmenumodel.cpp b/applets/appmenu/plugin/appmenumodel.cpp --- a/applets/appmenu/plugin/appmenumodel.cpp +++ b/applets/appmenu/plugin/appmenumodel.cpp @@ -175,7 +175,7 @@ if (KWindowSystem::isPlatformX11()) { auto *c = QX11Info::connection(); - auto getWindowPropertyString = [c, this](WId id, const QByteArray &name) -> QByteArray { + auto getWindowPropertyString = [c](WId id, const QByteArray &name) -> QByteArray { QByteArray value; if (!s_atoms.contains(name)) { const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData()); @@ -299,7 +299,7 @@ if (role == MenuRole) { // TODO this should be Qt::DisplayRole return actions.at(row)->text(); } else if (role == ActionRole) { - return qVariantFromValue((void *) actions.at(row)); + return QVariant::fromValue((void *) actions.at(row)); } return QVariant(); diff --git a/applets/batterymonitor/package/contents/ui/BatteryItem.qml b/applets/batterymonitor/package/contents/ui/BatteryItem.qml --- a/applets/batterymonitor/package/contents/ui/BatteryItem.qml +++ b/applets/batterymonitor/package/contents/ui/BatteryItem.qml @@ -50,7 +50,7 @@ id: brokenBatteryLabel width: parent ? parent.width : implicitWidth wrapMode: Text.WordWrap - text: batteryItem.isBroken && typeof model.Capacity !== "undefined" ? i18n("The capacity of this battery is %1%. This means it is broken and needs a replacement. Please contact your hardware vendor for more details.", model.Capacity) : "" + text: batteryItem.isBroken && typeof model.Capacity !== "undefined" ? i18n("This battery's health is at only %1% and should be replaced. Please contact your hardware vendor for more details.", model.Capacity) : "" font.pointSize: !!detailsLayout.parent.inListView ? theme.smallestFont.pointSize : theme.defaultFont.pointSize visible: batteryItem.isBroken } @@ -209,7 +209,6 @@ visible: !!item opacity: 0.5 sourceComponent: batteryDetails - active: batterymonitor.batteries.count < 2 } } diff --git a/applets/batterymonitor/package/contents/ui/BrightnessItem.qml b/applets/batterymonitor/package/contents/ui/BrightnessItem.qml --- a/applets/batterymonitor/package/contents/ui/BrightnessItem.qml +++ b/applets/batterymonitor/package/contents/ui/BrightnessItem.qml @@ -23,12 +23,16 @@ import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as Components +import org.kde.plasma.components 3.0 as Components3 RowLayout { + id: item property alias icon: brightnessIcon.source property alias label: brightnessLabel.text property alias value: brightnessSlider.value - property alias maximumValue: brightnessSlider.maximumValue + property alias maximumValue: brightnessSlider.to + property alias stepSize: brightnessSlider.stepSize + signal moved() spacing: units.gridUnit @@ -51,13 +55,14 @@ height: paintedHeight } - Components.Slider { + Components3.Slider { id: brightnessSlider width: parent.width // Don't allow the slider to turn off the screen // Please see https://git.reviewboard.kde.org/r/122505/ for more information - minimumValue: maximumValue > 100 ? 1 : 0 + from: to > 100 ? 1 : 0 stepSize: 1 + onMoved: item.moved() } } } diff --git a/applets/batterymonitor/package/contents/ui/CompactRepresentation.qml b/applets/batterymonitor/package/contents/ui/CompactRepresentation.qml --- a/applets/batterymonitor/package/contents/ui/CompactRepresentation.qml +++ b/applets/batterymonitor/package/contents/ui/CompactRepresentation.qml @@ -70,7 +70,7 @@ BadgeOverlay { anchors.fill: batteryIcon - text: batteryContainer.hasBattery ? i18nc("battery percentage below battery icon", "%1%", percent) : i18nc("short symbol to signal there is no battery curently available", "-") + text: batteryContainer.hasBattery ? i18nc("battery percentage below battery icon", "%1%", percent) : i18nc("short symbol to signal there is no battery currently available", "-") icon: batteryIcon visible: plasmoid.configuration.showPercentage } diff --git a/applets/batterymonitor/package/contents/ui/PopupDialog.qml b/applets/batterymonitor/package/contents/ui/PopupDialog.qml --- a/applets/batterymonitor/package/contents/ui/PopupDialog.qml +++ b/applets/batterymonitor/package/contents/ui/PopupDialog.qml @@ -38,18 +38,6 @@ signal powermanagementChanged(bool checked) - Component.onCompleted: { - // setup handler on slider value manually to avoid change on creation - - brightnessSlider.valueChanged.connect(function() { - batterymonitor.screenBrightness = brightnessSlider.value - }) - - keyboardBrightnessSlider.valueChanged.connect(function() { - batterymonitor.keyboardBrightness = keyboardBrightnessSlider.value - }) - } - Column { id: settingsColumn anchors.horizontalCenter: parent.horizontalCenter @@ -82,6 +70,9 @@ maximumValue: batterymonitor.maximumScreenBrightness KeyNavigation.tab: keyboardBrightnessSlider KeyNavigation.backtab: batteryList + stepSize: batterymonitor.maximumScreenBrightness/100 + + onMoved: batterymonitor.screenBrightness = value // Manually dragging the slider around breaks the binding Connections { @@ -102,6 +93,8 @@ KeyNavigation.tab: pmSwitch KeyNavigation.backtab: brightnessSlider + onMoved: batterymonitor.keyboardBrightness = value + // Manually dragging the slider around breaks the binding Connections { target: batterymonitor diff --git a/applets/batterymonitor/package/contents/ui/batterymonitor.qml b/applets/batterymonitor/package/contents/ui/batterymonitor.qml --- a/applets/batterymonitor/package/contents/ui/batterymonitor.qml +++ b/applets/batterymonitor/package/contents/ui/batterymonitor.qml @@ -40,12 +40,9 @@ return PlasmaCore.Types.ActiveStatus } - if (pmSource.data.Battery["Has Cumulative"]) { - if (pmSource.data.Battery.State !== "Charging" && pmSource.data.Battery.Percent <= 5) { - return PlasmaCore.Types.NeedsAttentionStatus - } else if (pmSource.data["Battery"]["State"] !== "FullyCharged") { - return PlasmaCore.Types.ActiveStatus - } + if (pmSource.data.Battery["Has Cumulative"] + && pmSource.data["Battery"]["State"] !== "FullyCharged") { + return PlasmaCore.Types.ActiveStatus } return PlasmaCore.Types.PassiveStatus @@ -105,6 +102,7 @@ readonly property var kcm_energyinfo: ["kcm_energyinfo.desktop"] readonly property bool kcmEnergyInformationAuthorized: KCMShell.authorize(kcm_energyinfo).length > 0 + property QtObject updateScreenBrightnessJob onScreenBrightnessChanged: { if (disableBrightnessUpdate) { return; @@ -114,17 +112,25 @@ operation.brightness = screenBrightness; // show OSD only when the plasmoid isn't expanded since the moving slider is feedback enough operation.silent = plasmoid.expanded - service.startOperationCall(operation); + updateScreenBrightnessJob = service.startOperationCall(operation); + updateScreenBrightnessJob.finished.connect(function(job) { + Logic.updateBrightness(batterymonitor, pmSource); + }); } + + property QtObject updateKeyboardBrightnessJob onKeyboardBrightnessChanged: { if (disableBrightnessUpdate) { return; } var service = pmSource.serviceForSource("PowerDevil"); var operation = service.operationDescription("setKeyboardBrightness"); operation.brightness = keyboardBrightness; operation.silent = plasmoid.expanded - service.startOperationCall(operation); + updateKeyboardBrightnessJob = service.startOperationCall(operation); + updateKeyboardBrightnessJob.finished.connect(function(job) { + Logic.updateBrightness(batterymonitor, pmSource); + }); } function action_powerdevilkcm() { diff --git a/applets/batterymonitor/package/contents/ui/logic.js b/applets/batterymonitor/package/contents/ui/logic.js --- a/applets/batterymonitor/package/contents/ui/logic.js +++ b/applets/batterymonitor/package/contents/ui/logic.js @@ -42,27 +42,17 @@ } if (batteryData["Is Power Supply"] && batteryData["Capacity"] != "" && typeof batteryData["Capacity"] == "number") { - data.push({label: i18n("Capacity:") }) - data.push({value: i18nc("Placeholder is battery capacity", "%1%", batteryData["Capacity"]) }) - } - - // Non-powersupply batteries have a name consisting of the vendor and model already - if (batteryData["Is Power Supply"]) { - if (batteryData["Vendor"] != "" && typeof batteryData["Vendor"] == "string") { - data.push({label: i18n("Vendor:") }) - data.push({value: batteryData["Vendor"] }) - } - - if (batteryData["Product"] != "" && typeof batteryData["Product"] == "string") { - data.push({label: i18n("Model:") }) - data.push({value: batteryData["Product"] }) - } + data.push({label: i18n("Battery Health:") }) + data.push({value: i18nc("Placeholder is battery health percentage", "%1%", batteryData["Capacity"]) }) } return data } function updateBrightness(rootItem, source) { + if (rootItem.updateScreenBrightnessJob || rootItem.updateKeyboardBrightnessJob) + return; + if (!source.data["PowerDevil"]) { return; } diff --git a/applets/batterymonitor/package/metadata.desktop b/applets/batterymonitor/package/metadata.desktop --- a/applets/batterymonitor/package/metadata.desktop +++ b/applets/batterymonitor/package/metadata.desktop @@ -1,8 +1,9 @@ [Desktop Entry] Name=Battery and Brightness Name[ar]=البطّاريّة والسّطوع +Name[ast]=Batería y brilléu Name[ca]=Bateria i lluminositat -Name[ca@valencia]=Bateria i lluminositat +Name[ca@valencia]=Bateria i brillantor Name[cs]=Baterie a Jas Name[da]=Batteri og lysstyrke Name[de]=Akku und Bildschirmhelligkeit @@ -21,7 +22,8 @@ Name[it]=Batteria e luminosità Name[ja]=バッテリーと明るさ Name[ko]=배터리와 밝기 -Name[lt]=Baterija ir ryškumas +Name[lt]=Akumuliatorius ir ryškumas +Name[lv]=Baterija un gaišums Name[nl]=Batterij en helderheid Name[nn]=Batteri og lysstyrke Name[pa]=ਬੈਟਰੀ ਤੇ ਚਮਕ @@ -36,13 +38,15 @@ Name[sr@ijekavianlatin]=Baterija i osvjetljaj Name[sr@latin]=Baterija i osvetljaj Name[sv]=Batteri och ljusstyrka +Name[tg]=Батарея ва дурахшонӣ Name[tr]=Pil ve Ekran Parlaklığı Name[uk]=Акумулятор і яскравість дисплея Name[x-test]=xxBattery and Brightnessxx Name[zh_CN]=电池和亮度 Name[zh_TW]=電池與亮度 Comment=See the power status of your battery Comment[ar]=طالع حالة طاقة البطارية +Comment[ast]=Visualiza l'estáu de la carga de la batería Comment[be@latin]=Naziraje za kolkaściu enerhii ŭ tvajoj batarei Comment[bg]=Показва състоянието на батерията Comment[bs]=Pazite na stanje popunjenosti baterije @@ -57,7 +61,7 @@ Comment[eo]=Montri energian staton de viaj baterioj Comment[es]=Mire el estado de potencia de su batería Comment[et]=Aku oleku näitaja -Comment[eu]=Ikus bateriaren energia-egoera +Comment[eu]=Ikusi zure bateriaren energia-egoera Comment[fi]=Näyttää akun virtatilanteen Comment[fr]=Voir le niveau d'énergie de votre batterie Comment[fy]=Besjoch de stroomtastân fan jo batterij @@ -79,7 +83,7 @@ Comment[km]=មើល​ស្ថានភាព​ថាមពល​នៃ​ថ្ម​របស់​អ្នក Comment[kn]=ನಿನ್ನ ವಿದ್ಯುತ್ಕೋಶದ (ಬಾಟರಿ) ಸ್ಥಿತಿಯನ್ನು ನೋಡು Comment[ko]=배터리 상태를 표시합니다 -Comment[lt]=Rodyti akumuliatoriaus įkrovimą +Comment[lt]=Rodyti akumuliatoriaus įkrovą Comment[lv]=Rāda baterijas izlādes stāvokli Comment[mk]=Видете го енергетскиот статус на вашата батерија Comment[ml]=നിങ്ങളുടെ ബാറ്ററിയുടെ വൈദ്യുതി ഊര്‍ജ്ജ സ്ഥിതി കാണുക @@ -104,7 +108,7 @@ Comment[sv]=Se batteriets laddningsstatus Comment[ta]=See the power status of your battery Comment[te]=మీ బ్యాటరీ యొక్క పవర్ స్థితిని చూడుము -Comment[tg]=Намоиши ҳолати барқи батарея +Comment[tg]=Вазъияти қувваи барқи батареяро назорат кунед Comment[th]=แสดงสถานะพลังงานของแบตเตอรี่ของคุณ Comment[tr]=Pilinizin güç durumuna bakın Comment[ug]=توكدانىڭىزنىڭ توك ھالىتىنى كۆرسىتىدۇ @@ -115,6 +119,7 @@ Comment[zh_TW]=查看您的電池的電力狀態 Keywords=Power Management;Battery;System;Energy; Keywords[ar]=إدارة الطاقة;بطارية;نظام;طاقة; +Keywords[ast]=Xestión d'enerxía;Batería;Sistema;Enerxía; Keywords[bs]=Upravljanje napajenjem;Baterija;Sistem;Energija; Keywords[ca]=Sistema de gestió d'energia;Bateria;Sistema;Energia; Keywords[ca@valencia]=Sistema de gestió d'energia;Bateria;Sistema;Energia; @@ -138,7 +143,7 @@ Keywords[ja]=電源管理;バッテリー;システム;エネルギー; Keywords[kk]=Power Management;Battery;System;Energy; Keywords[ko]=Power Management;Battery;System;Energy;전원 관리;전력 관리;배터리;시스템;에너지; -Keywords[lt]=Energijos valdymas;Baterija;Sistema;Energija;Akumuliatorius; +Keywords[lt]=Energijos valdymas;Baterija;Sistema;Energija;Akumuliatorius;Maitinimo valdymas Keywords[mr]=वीज व्यवस्थापन; बॅटरी; प्रणाली; ऊर्जा; Keywords[nb]=Strømstyring; Batteri; System; Energi; Keywords[nds]=Stroompleeg,Batterie,Systeem,Energie @@ -163,7 +168,7 @@ Keywords[zh_CN]=电源管理;电池;系统;能源; Keywords[zh_TW]=Power Management;Battery;System;Energy; -Icon=battery +Icon=battery-full Type=Service X-KDE-ServiceTypes=Plasma/Applet @@ -180,7 +185,7 @@ X-KDE-PluginInfo-License=GPL-2.0+ X-KDE-PluginInfo-Name=org.kde.plasma.battery X-KDE-PluginInfo-Version=3.0 -X-KDE-PluginInfo-Website=http://vizZzion.org +X-KDE-PluginInfo-Website=https://vizzzion.org X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-EnabledByDefault=true X-Plasma-DBusActivationService=org.kde.Solid.PowerManagement diff --git a/applets/calendar/package/metadata.desktop b/applets/calendar/package/metadata.desktop --- a/applets/calendar/package/metadata.desktop +++ b/applets/calendar/package/metadata.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Calendar Name[ar]=التقويم +Name[ast]=Calendariu Name[be@latin]=Kalandar Name[bg]=Календар Name[bn]=ক্যালেণ্ডার @@ -85,6 +86,7 @@ Comment[el]=Εμφάνιση συναντήσεων και συμβάντων ανά μήνα Comment[en_GB]=Month display with your appointments and events Comment[es]=Calendario mensual con sus citas y eventos +Comment[et]=Kuuvaade ühes kõigi kohtumiste ja sündmustega Comment[eu]=Hileko ikuspegia zure hitzordu eta gertaerekin Comment[fi]=Kalenterimerkintöjesi kuukausinäkymä Comment[fr]=Vue mensuelle avec vos rendez-vous et évènements diff --git a/applets/clipboard/contents/ui/ClipboardPage.qml b/applets/clipboard/contents/ui/ClipboardPage.qml --- a/applets/clipboard/contents/ui/ClipboardPage.qml +++ b/applets/clipboard/contents/ui/ClipboardPage.qml @@ -82,7 +82,7 @@ level: 3 opacity: 0.6 visible: clipboardMenu.model.count === 0 && filter.length === 0 - text: i18n("Clipboard history is empty.") + text: i18n("Clipboard is empty") } RowLayout { diff --git a/applets/clipboard/contents/ui/ImageItemDelegate.qml b/applets/clipboard/contents/ui/ImageItemDelegate.qml --- a/applets/clipboard/contents/ui/ImageItemDelegate.qml +++ b/applets/clipboard/contents/ui/ImageItemDelegate.qml @@ -24,7 +24,8 @@ KQuickControlsAddons.QPixmapItem { id: previewPixmap - height: Math.round(width * (nativeHeight/nativeWidth) + units.smallSpacing * 2) + width: Math.min(nativeWidth, width) + height: Math.min(nativeHeight, Math.round(width * (nativeHeight/nativeWidth) + units.smallSpacing * 2)) pixmap: DecorationRole fillMode: KQuickControlsAddons.QPixmapItem.PreserveAspectFit } diff --git a/applets/clipboard/metadata.desktop b/applets/clipboard/metadata.desktop --- a/applets/clipboard/metadata.desktop +++ b/applets/clipboard/metadata.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Clipboard Name[ar]=الحافظة +Name[ast]=Cartafueyu Name[bs]=Klipbord Name[ca]=Porta-retalls Name[ca@valencia]=Porta-retalls @@ -23,6 +24,7 @@ Name[ja]=クリップボード Name[ko]=클립보드 Name[lt]=Iškarpinė +Name[lv]=Starpliktuve Name[nb]=Utklippstavle Name[nds]=Twischenaflaag Name[nl]=Klembord @@ -39,6 +41,7 @@ Name[sr@ijekavianlatin]=klipbord Name[sr@latin]=klipbord Name[sv]=Klippbord +Name[tg]=Ҳофизаи муваққатӣ Name[tr]=Pano Name[uk]=Буфер обміну Name[x-test]=xxClipboardxx @@ -54,14 +57,16 @@ Comment[el]=Παρέχει πρόσβαση στο ιστορικό του προχείρου Comment[en_GB]=Provides access to the clipboard history Comment[es]=Proporciona acceso al historial del portapapeles +Comment[et]=Lõikepuhvri ajaloo kasutamine Comment[eu]=Arbeleko historiara sarbidea ematen du Comment[fi]=Tarjoaa pääsyn leikepöydän historiaan Comment[fr]=Donne accès à l'historique du presse-papiers Comment[gl]=Fornece acceso ao historial do portapapeis. Comment[hu]=Hozzáférést nyújt a vágólap előzményeihez Comment[id]=Menyediakan akses ke histori papan-klip Comment[it]=Fornisce l'accesso alla cronologia degli appunti Comment[ko]=클립보드 기록 표시 +Comment[lt]=Suteikia prieigą prie iškarpinės istorijos Comment[nl]=Biedt toegang tot de geschiedenis van klembord Comment[nn]=Gjev tilgang til utklippstavleloggen Comment[pa]=ਕਲਿੱਪਬੋਰਡ ਅਤੀਤ ਲਈ ਪਹੁੰਚ ਦਿੰਦਾ ਹੈ diff --git a/applets/devicenotifier/package/contents/ui/devicenotifier.qml b/applets/devicenotifier/package/contents/ui/devicenotifier.qml --- a/applets/devicenotifier/package/contents/ui/devicenotifier.qml +++ b/applets/devicenotifier/package/contents/ui/devicenotifier.qml @@ -100,8 +100,11 @@ source: devicenotifier.popupIcon width: units.iconSizes.medium; height: units.iconSizes.medium; + active: compactMouse.containsMouse MouseArea { + id: compactMouse anchors.fill: parent + hoverEnabled: true onClicked: plasmoid.expanded = !plasmoid.expanded } } diff --git a/applets/devicenotifier/package/metadata.desktop b/applets/devicenotifier/package/metadata.desktop --- a/applets/devicenotifier/package/metadata.desktop +++ b/applets/devicenotifier/package/metadata.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Device Notifier Name[ar]=مُخطِر الأجهزة +Name[ast]=Avisador de preseos Name[be@latin]=Infarmavańnie pra novyja pryłady Name[bg]=Уведомяване за нови устройства Name[bn]=ডিভাইস নোটিফায়ার @@ -65,7 +66,6 @@ Name[sr@latin]=izveštavač o uređajima Name[sv]=Underrättelse om enheter Name[ta]=கருவி நோட்டம் -Name[tg]=Извещение о новых устройствах Name[th]=แจ้งให้ทราบถึงอุปกรณ์ Name[tr]=Aygıt Bildirici Name[ug]=ئۈسكۈنە ئۇقتۇرۇشى @@ -91,7 +91,7 @@ Comment[eo]=Atentigoj kaj atingo por novaj aparatoj Comment[es]=Notificaciones y accesos de nuevos dispositivos Comment[et]=Uutest seadmetest teatamine ja nende kasutamine -Comment[eu]=Gailu berrien jakinarazpenak eta sarbideak +Comment[eu]=Jakinarazpenak eta gailu berrietara sarbidea Comment[fi]=Ilmoitukset ja pääsy uusille laitteille Comment[fr]=Notifications et accès aux nouveaux périphériques Comment[fy]=Notifikaasjes en tagong foar nije apparaten @@ -114,7 +114,7 @@ Comment[kn]=ಹೊಸ ಸಾಧನಗಳಿಗೆ ಸೂಚನೆಗಳು ಮತ್ತು ನಿಲುಕಣೆ Comment[ko]=새 장치가 연결된 것을 알려 주고 접근할 수 있도록 합니다 Comment[ku]=Hişyarî û gihîştin ji bo cîhazên nû -Comment[lt]=Pranešimų apie įrenginius ir jų atvėrimo priemonė +Comment[lt]=Pranešimų apie įrenginius ir prieiga prie jų Comment[lv]=Paziņojumi un piekļuve jaunām ierīcēm Comment[mk]=Известувања и пристап до нови уреди Comment[ml]=പുതിയ ഉപകരണങ്ങളുടെ അറിയിപ്പുകളും സമീപനവും @@ -139,7 +139,6 @@ Comment[sv]=Underrättelser och åtkomst av nya enheter Comment[ta]=Notifications and access for new devices Comment[te]=కొత్త పరికరముల కొరకు నోటీసులు మరియు యాక్సెస్ -Comment[tg]=Огоҳиҳо ва дастрасӣ ба дастгоҳҳои нав Comment[th]=การแจ้งให้ทราบและเข้าใช้งานอุปกรณ์ใหม่ Comment[tr]=Yeni donanım bildirimleri ve yeni donanımlara erişim Comment[ug]=يېڭى ئۈسكۈنە ئۇقتۇرۇشى ۋە زىيارىتى @@ -164,7 +163,7 @@ X-KDE-PluginInfo-Email=wilderkde@gmail.com X-KDE-PluginInfo-Name=org.kde.plasma.devicenotifier X-KDE-PluginInfo-Version=1.0 -X-KDE-PluginInfo-Website=http://userbase.kde.org/Plasma/DeviceNotifier +X-KDE-PluginInfo-Website=https://userbase.kde.org/Plasma/DeviceNotifier X-KDE-PluginInfo-Category=System Information X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL-2.0+ diff --git a/applets/devicenotifier/test-predicate-openinwindow.desktop b/applets/devicenotifier/test-predicate-openinwindow.desktop --- a/applets/devicenotifier/test-predicate-openinwindow.desktop +++ b/applets/devicenotifier/test-predicate-openinwindow.desktop @@ -38,8 +38,8 @@ Name[km]=បើក​ជា​មួយ​កម្មវិធី​គ្រប់គ្រង​ឯកសារ Name[kn]=ಕಡತ ವ್ಯವಸ್ಥಾಪಕದೊಂದಿಗೆ ತೆರೆ Name[ko]=파일 관리자로 열기 -Name[lt]=Atverti su failų tvarkykle -Name[lv]=Atvērt failu pārvaldniekā +Name[lt]=Atverti naudojant failų tvarkytuvę +Name[lv]=Atvērt datņu pārvaldniekā Name[mk]=Отвори со менаџер на датотеки Name[ml]=ഫയലുകളുടെ നടത്തിപ്പുകാരനില്‍ തുറക്കുക Name[mr]=फाईल व्यवस्थापकात उघडा @@ -61,7 +61,7 @@ Name[sr@ijekavianlatin]=Otvori menadžerom fajlova Name[sr@latin]=Otvori menadžerom fajlova Name[sv]=Öppna med filhanterare -Name[tg]=Кушодан бо намоишгари Интернети &стандартӣ +Name[tg]=Кушодан ба воситаи мудири файлҳо Name[th]=เปิดใช้งานผ่านเครื่องมือจัดการแฟ้ม Name[tr]=Dosya yöneticisi ile aç Name[ug]=ھۆججەت باشقۇرغۇدا ئاچ diff --git a/applets/digital-clock/package/contents/config/config.qml b/applets/digital-clock/package/contents/config/config.qml --- a/applets/digital-clock/package/contents/config/config.qml +++ b/applets/digital-clock/package/contents/config/config.qml @@ -35,7 +35,7 @@ } ConfigCategory { name: i18n("Calendar") - icon: "view-calendar" + icon: "office-calendar" source: "configCalendar.qml" } ConfigCategory { diff --git a/applets/digital-clock/package/contents/ui/CalendarView.qml b/applets/digital-clock/package/contents/ui/CalendarView.qml --- a/applets/digital-clock/package/contents/ui/CalendarView.qml +++ b/applets/digital-clock/package/contents/ui/CalendarView.qml @@ -33,6 +33,8 @@ property int _minimumHeight: units.gridUnit * 14 Layout.preferredWidth: _minimumWidth Layout.preferredHeight: _minimumHeight * 1.5 + Layout.maximumWidth: Layout.preferredWidth + Layout.maximumHeight: Layout.preferredHeight readonly property bool showAgenda: PlasmaCalendar.EventPluginsManager.enabledPlugins.length > 0 diff --git a/applets/digital-clock/package/contents/ui/DigitalClock.qml b/applets/digital-clock/package/contents/ui/DigitalClock.qml --- a/applets/digital-clock/package/contents/ui/DigitalClock.qml +++ b/applets/digital-clock/package/contents/ui/DigitalClock.qml @@ -301,13 +301,13 @@ PropertyChanges { target: dateLabel - // this can be marginal bigger than contentHeight because of the horizontal fit - height: sizehelper.contentHeight + height: dateLabel.paintedHeight width: main.width fontSizeMode: Text.Fit minimumPixelSize: Math.min(0.7 * theme.smallestFont.pixelSize, timeLabel.height) elide: Text.ElideRight + wrapMode: Text.WordWrap } AnchorChanges { @@ -427,7 +427,7 @@ var newIndex = main.tzIndex; wheelDelta += delta; // magic number 120 for common "one click" - // See: http://qt-project.org/doc/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop + // See: https://doc.qt.io/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop while (wheelDelta >= 120) { wheelDelta -= 120; newIndex--; diff --git a/applets/digital-clock/package/contents/ui/configAppearance.qml b/applets/digital-clock/package/contents/ui/configAppearance.qml --- a/applets/digital-clock/package/contents/ui/configAppearance.qml +++ b/applets/digital-clock/package/contents/ui/configAppearance.qml @@ -29,8 +29,6 @@ QtLayouts.ColumnLayout { id: appearancePage - width: childrenRect.width - height: childrenRect.height signal configurationChanged @@ -143,48 +141,63 @@ Kirigami.FormData.isSection: true } - QtControls.ComboBox { - id: dateFormat + QtLayouts.RowLayout { Kirigami.FormData.label: i18n("Date format:") enabled: showDate.checked - textRole: "label" - model: [ - { - 'label': i18n("Long Date"), - 'name': "longDate" - }, - { - 'label': i18n("Short Date"), - 'name': "shortDate" - }, - { - 'label': i18n("ISO Date"), - 'name': "isoDate" - }, - { - 'label': i18nc("custom date format", "Custom"), - 'name': "custom" - } - ] - onCurrentIndexChanged: cfg_dateFormat = model[currentIndex]["name"] - Component.onCompleted: { - for (var i = 0; i < model.length; i++) { - if (model[i]["name"] === plasmoid.configuration.dateFormat) { - dateFormat.currentIndex = i; + QtControls.ComboBox { + id: dateFormat + textRole: "label" + model: [ + { + 'label': i18n("Long Date"), + 'name': "longDate", + format: Qt.SystemLocaleLongDate + }, + { + 'label': i18n("Short Date"), + 'name': "shortDate", + format: Qt.SystemLocaleShortDate + }, + { + 'label': i18n("ISO Date"), + 'name': "isoDate", + format: Qt.ISODate + }, + { + 'label': i18nc("custom date format", "Custom"), + 'name': "custom" + } + ] + onCurrentIndexChanged: cfg_dateFormat = model[currentIndex]["name"] + + Component.onCompleted: { + for (var i = 0; i < model.length; i++) { + if (model[i]["name"] === plasmoid.configuration.dateFormat) { + dateFormat.currentIndex = i; + } } } } + + QtControls.Label { + QtLayouts.Layout.fillWidth: true + textFormat: Text.PlainText + text: Qt.formatDate(new Date(), cfg_dateFormat === "custom" ? customDateFormat.text + : dateFormat.model[dateFormat.currentIndex].format) + } } QtControls.TextField { id: customDateFormat QtLayouts.Layout.fillWidth: true + enabled: showDate.checked visible: cfg_dateFormat == "custom" } QtControls.Label { - text: i18n("Time Format Documentation") + text: i18n("Time Format Documentation") + enabled: showDate.checked visible: cfg_dateFormat == "custom" wrapMode: Text.Wrap QtLayouts.Layout.preferredWidth: QtLayouts.Layout.maximumWidth @@ -233,7 +246,7 @@ } icon.name: "format-text-bold" checkable: true - Accessible.name: tooltip + Accessible.name: QtControls.ToolTip.text } QtControls.Button { @@ -243,7 +256,7 @@ } icon.name: "format-text-italic" checkable: true - Accessible.name: tooltip + Accessible.name: QtControls.ToolTip.text } } } diff --git a/applets/digital-clock/package/contents/ui/configTimeZones.qml b/applets/digital-clock/package/contents/ui/configTimeZones.qml --- a/applets/digital-clock/package/contents/ui/configTimeZones.qml +++ b/applets/digital-clock/package/contents/ui/configTimeZones.qml @@ -18,19 +18,17 @@ * along with this program. If not, see */ -import QtQuick 2.0 -import QtQuick.Controls 1.2 as QtControls +import QtQuick 2.13 +import QtQuick.Controls 2.8 as QQC2 import QtQuick.Layouts 1.0 import QtQuick.Dialogs 1.1 import org.kde.plasma.private.digitalclock 1.0 import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.kirigami 2.5 as Kirigami -Item { +ColumnLayout { id: timeZonesPage - width: parent.width - height: parent.height property alias cfg_selectedTimeZones: timeZones.selectedTimeZones property alias cfg_wheelChangesTimezone: enableWheelCheckBox.checked @@ -47,163 +45,63 @@ } } - // This is just for getting the column width - QtControls.CheckBox { - id: checkbox - visible: false + Kirigami.InlineMessage { + id: messageWidget + Layout.fillWidth: true + Layout.margins: Kirigami.Units.smallSpacing + type: Kirigami.MessageType.Warning + text: i18n("At least one time zone needs to be enabled. 'Local' was enabled automatically.") + showCloseButton: true } + QQC2.TextField { + id: filter + Layout.fillWidth: true + placeholderText: i18n("Search Time Zones") + } - ColumnLayout { - anchors.fill: parent - - Rectangle { - id: messageWidget - - anchors { - left: parent.left - right: parent.right - margins: 1 - } - - height: 0 - - //TODO: This is the actual color KMessageWidget uses as its base color but here it gives - // a different color, figure out why - //property color gradBaseColor: Qt.rgba(0.69, 0.5, 0, 1) - gradient: Gradient { - GradientStop { position: 0.0; color: "#FFD86D" } //Qt.lighter(messageWidget.gradBaseColor, 1.1) - GradientStop { position: 0.1; color: "#EAC360" } // messageWidget.gradBaseColor - GradientStop { position: 1.0; color: "#CAB064" } //Qt.darker(messageWidget.gradBaseColor, 1.1) - } - - radius: 5 - border.width: 1 - border.color: "#79735B" - - visible: false + Item { + Layout.fillWidth: true + Layout.fillHeight: true - Behavior on visible { - ParallelAnimation { - PropertyAnimation { - target: messageWidget - property: "opacity" - to: messageWidget.visible ? 0 : 1.0 - easing.type: Easing.Linear - } - PropertyAnimation { - target: messageWidget - property: "Layout.minimumHeight" - to: messageWidget.visible ? 0 : messageWidgetLabel.height + (2 *units.largeSpacing) - easing.type: Easing.Linear - } - } - } + QQC2.ScrollView { + anchors.fill: parent + clip: true + Component.onCompleted: background.visible = true // enable border - RowLayout { - anchors.fill: parent - anchors.margins: units.largeSpacing - anchors.leftMargin: units.smallSpacing - anchors.rightMargin: units.smallSpacing - spacing: units.smallSpacing - - PlasmaCore.IconItem { - anchors.verticalCenter: parent.verticalCenter - height: units.iconSizes.smallMedium - width: height - source: "dialog-warning" - } + ListView { + id: listView + focus: true // keyboard navigation + activeFocusOnTab: true // keyboard navigation - QtControls.Label { - id: messageWidgetLabel - anchors.verticalCenter: parent.verticalCenter - Layout.fillWidth: true - text: i18n("At least one time zone needs to be enabled. 'Local' was enabled automatically.") - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap + model: TimeZoneFilterProxy { + sourceModel: timeZones + filterString: filter.text } - PlasmaComponents.ToolButton { - anchors.verticalCenter: parent.verticalCenter - iconName: "dialog-close" - flat: true - - onClicked: { - messageWidget.visible = false; + delegate: QQC2.CheckDelegate { + id: checkbox + focus: true // keyboard navigation + width: parent.width + text: !city || city.indexOf("UTC") === 0 ? comment : comment ? i18n("%1, %2 (%3)", city, region, comment) : i18n("%1, %2", city, region) + checked: model.checked + onToggled: { + model.checked = checkbox.checked + listView.currentIndex = index // highlight + listView.forceActiveFocus() // keyboard navigation } + highlighted: ListView.isCurrentItem } } } + } - QtControls.TextField { - id: filter - Layout.fillWidth: true - placeholderText: i18n("Search Time Zones") - } - - QtControls.TableView { - id: timeZoneView - - signal toggleCurrent - - Layout.fillWidth: true - Layout.fillHeight: true - - Keys.onSpacePressed: toggleCurrent() - - model: TimeZoneFilterProxy { - sourceModel: timeZones - filterString: filter.text - } - - QtControls.TableViewColumn { - role: "checked" - width: checkbox.width - delegate: - QtControls.CheckBox { - id: checkBox - anchors.centerIn: parent - checked: styleData.value - activeFocusOnTab: false // only let the TableView as a whole get focus - onClicked: { - //needed for model's setData to be called - model.checked = checked; - } - - Connections { - target: timeZoneView - onToggleCurrent: { - if (styleData.row === timeZoneView.currentRow) { - model.checked = !checkBox.checked - } - } - } - } - - resizable: false - movable: false - } - QtControls.TableViewColumn { - role: "city" - title: i18n("City") - } - QtControls.TableViewColumn { - role: "region" - title: i18n("Region") - } - QtControls.TableViewColumn { - role: "comment" - title: i18n("Comment") - } - } - - RowLayout { - Layout.fillWidth: true - QtControls.CheckBox { - id: enableWheelCheckBox - text: i18n("Switch time zone with mouse wheel") - } + RowLayout { + Layout.fillWidth: true + QQC2.CheckBox { + id: enableWheelCheckBox + text: i18n("Switch time zone with mouse wheel") } - } + } diff --git a/applets/digital-clock/package/contents/ui/main.qml b/applets/digital-clock/package/contents/ui/main.qml --- a/applets/digital-clock/package/contents/ui/main.qml +++ b/applets/digital-clock/package/contents/ui/main.qml @@ -32,6 +32,7 @@ width: units.gridUnit * 10 height: units.gridUnit * 4 property string dateFormatString: setDateFormatString() + Plasmoid.backgroundHints: PlasmaCore.Types.ShadowBackground | PlasmaCore.Types.ConfigurableBackground property date tzDate: { // get the time for the given timezone from the dataengine var now = dataSource.data[plasmoid.configuration.lastSelectedTimezone]["DateTime"]; diff --git a/applets/digital-clock/package/metadata.desktop b/applets/digital-clock/package/metadata.desktop --- a/applets/digital-clock/package/metadata.desktop +++ b/applets/digital-clock/package/metadata.desktop @@ -2,6 +2,7 @@ Name=Digital Clock Name[af]=Digitale horlosie Name[ar]=ساعة رقمية +Name[ast]=Reló dixital Name[be]=Лічбавы гадзіннік Name[be@latin]=Ličbavy hadzińnik Name[bg]=Цифров часовник @@ -97,7 +98,7 @@ Comment[en_GB]=Time displayed in a digital format Comment[es]=La hora mostrada en formato digital Comment[et]=Aja esitamine digitaalsel kujul -Comment[eu]=Ordua formatu digitalean bistaratua +Comment[eu]=Ordua formatu digitalean azaldua Comment[fi]=Aika näytettynä digitaalimuodossa Comment[fr]=L'heure au format numérique Comment[gl]=A hora mostrada nun formato dixital diff --git a/applets/digital-clock/plugin/CMakeLists.txt b/applets/digital-clock/plugin/CMakeLists.txt --- a/applets/digital-clock/plugin/CMakeLists.txt +++ b/applets/digital-clock/plugin/CMakeLists.txt @@ -2,7 +2,7 @@ find_package(IsoCodes) set_package_properties(IsoCodes PROPERTIES DESCRIPTION "ISO language, territory, currency, script codes and their translations" - URL "http://pkg-isocodes.alioth.debian.org/" + URL "https://salsa.debian.org/iso-codes-team/iso-codes" PURPOSE "Translation of country names in digital clock applet" TYPE RUNTIME ) diff --git a/applets/digital-clock/plugin/clipboardmenu.cpp b/applets/digital-clock/plugin/clipboardmenu.cpp --- a/applets/digital-clock/plugin/clipboardmenu.cpp +++ b/applets/digital-clock/plugin/clipboardmenu.cpp @@ -97,7 +97,7 @@ a->setData(s); if (m_secondsIncluded) { s = time.toString(Qt::SystemLocaleLongDate); - s.replace(rx, QString()); + s.remove(rx); a = menu->addAction(s); a->setData(s); s = time.toString(Qt::SystemLocaleLongDate); diff --git a/applets/icon/iconapplet.cpp b/applets/icon/iconapplet.cpp --- a/applets/icon/iconapplet.cpp +++ b/applets/icon/iconapplet.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -110,7 +111,7 @@ connect(statJob, &KIO::StatJob::finished, this, [=] { QString desiredDesktopFileName = m_url.fileName(); - // in doubt, just encode the entire URL, e.g. http://www.kde.org/ has no filename + // in doubt, just encode the entire URL, e.g. https://www.kde.org/ has no filename if (desiredDesktopFileName.isEmpty()) { desiredDesktopFileName = KIO::encodeFileName(m_url.toDisplayString()); } @@ -121,9 +122,9 @@ } QString backingDesktopFile = plasmaIconsFolderPath + QLatin1Char('/'); - // KIO::suggestName always appends a suffix, i.e. it expects that we already know the file already exists + // KFileUtils::suggestName always appends a suffix, i.e. it expects that we already know the file already exists if (QFileInfo::exists(backingDesktopFile + desiredDesktopFileName)) { - desiredDesktopFileName = KIO::suggestName(QUrl::fromLocalFile(plasmaIconsFolderPath), desiredDesktopFileName); + desiredDesktopFileName = KFileUtils::suggestName(QUrl::fromLocalFile(plasmaIconsFolderPath), desiredDesktopFileName); } backingDesktopFile.append(desiredDesktopFileName); @@ -181,7 +182,7 @@ } // KFileItem might return "." as text for e.g. root folders - if (name == QLatin1String(".")) { + if (name == QLatin1Char('.')) { name.clear(); } @@ -387,7 +388,7 @@ if (!m_openContainingFolderAction) { if (KProtocolManager::supportsListing(linkUrl)) { m_openContainingFolderAction = new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Containing Folder"), this); - connect(m_openContainingFolderAction, &QAction::triggered, this, [this, linkUrl] { + connect(m_openContainingFolderAction, &QAction::triggered, this, [ linkUrl] { KIO::highlightInFileManager({linkUrl}); }); } diff --git a/applets/icon/package/metadata.desktop b/applets/icon/package/metadata.desktop --- a/applets/icon/package/metadata.desktop +++ b/applets/icon/package/metadata.desktop @@ -3,6 +3,7 @@ Name[af]=Ikoon Name[ar]=أيقونة Name[as]=আইকন +Name[ast]=Iconu Name[be@latin]=Ikona Name[bg]=Икона Name[bn]=আইকন @@ -43,7 +44,7 @@ Name[kn]=ಚಿಹ್ನೆ Name[ko]=아이콘 Name[ku]=Îkon -Name[lt]=Ženkliukas +Name[lt]=Piktograma Name[lv]=Ikona Name[mai]=चिह्न Name[mk]=Икона @@ -86,6 +87,7 @@ Name[zh_TW]=圖示 Comment=A generic icon Comment[ar]=أيقونة عامّة +Comment[ast]=Un iconu xenéricu Comment[be@latin]=Prostaja ikona Comment[bg]=Обикновена икона Comment[bn_IN]=একটি সাধারণ আইকন @@ -124,7 +126,7 @@ Comment[kn]=ಒಂದು ಅಲಿಪ್ತ (ಜೆನೆರಿಕ್) ಚಿಹ್ನೆ Comment[ko]=일반적인 아이콘 Comment[ku]=Îkoneke jenerîk -Comment[lt]=Bendro pobūdžio ženkliukas +Comment[lt]=Bendrinė piktograma Comment[lv]=Vispārīga ikona Comment[mk]=Општа икона Comment[ml]=സാധാരണ പ്രതിരൂപം @@ -149,7 +151,6 @@ Comment[sr@latin]=Generička ikonica Comment[sv]=En generell ikon Comment[ta]=பொதுவான ஓவம் -Comment[tg]=Нишонаи умумӣ Comment[th]=ภาพไอคอนทั่วไป Comment[tr]=Bir genel simge Comment[ug]=ئادەتتىكى سىنبەلگە diff --git a/applets/kicker/CMakeLists.txt b/applets/kicker/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/applets/kicker/CMakeLists.txt @@ -0,0 +1,88 @@ +add_definitions( + -DQT_USE_QSTRINGBUILDER + -DQT_NO_CAST_TO_ASCII +# -DQT_NO_CAST_FROM_ASCII + -DQT_STRICT_ITERATORS + -DQT_NO_URL_CAST_FROM_STRING + -DQT_NO_CAST_FROM_BYTEARRAY + -DQT_NO_SIGNALS_SLOTS_KEYWORDS + -DQT_USE_FAST_OPERATOR_PLUS + -DTRANSLATION_DOMAIN=\"libkicker\" +) + +set(kickerplugin_SRCS + plugin/abstractentry.cpp + plugin/abstractmodel.cpp + plugin/actionlist.cpp + plugin/appentry.cpp + plugin/appsmodel.cpp + plugin/computermodel.cpp + plugin/contactentry.cpp + plugin/containmentinterface.cpp + plugin/draghelper.cpp + plugin/simplefavoritesmodel.cpp + plugin/kastatsfavoritesmodel.cpp + plugin/fileentry.cpp + plugin/forwardingmodel.cpp + plugin/placeholdermodel.cpp + plugin/funnelmodel.cpp + plugin/dashboardwindow.cpp + plugin/kickerplugin.cpp + plugin/menuentryeditor.cpp + plugin/processrunner.cpp + plugin/rootmodel.cpp + plugin/runnermodel.cpp + plugin/runnermatchesmodel.cpp + plugin/recentcontactsmodel.cpp + plugin/recentusagemodel.cpp + plugin/submenu.cpp + plugin/systementry.cpp + plugin/systemmodel.cpp + plugin/systemsettings.cpp + plugin/wheelinterceptor.cpp + plugin/windowsystem.cpp + plugin/funnelmodel.cpp +) + +ecm_qt_declare_logging_category(kickerplugin_SRCS + HEADER debug.h + IDENTIFIER KICKER_DEBUG + CATEGORY_NAME org.kde.plasma.kicker) + +qt5_add_dbus_interface(kickerplugin_SRCS ${CMAKE_SOURCE_DIR}/krunner/dbus/org.kde.krunner.App.xml krunner_interface) +qt5_add_dbus_interface(kickerplugin_SRCS ${CMAKE_SOURCE_DIR}/ksmserver/org.kde.KSMServerInterface.xml ksmserver_interface) + +install(FILES plugin/qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/kicker) + +add_library(kickerplugin SHARED ${kickerplugin_SRCS}) + +target_link_libraries(kickerplugin + Qt5::Core + Qt5::DBus + Qt5::Qml + Qt5::Quick + Qt5::X11Extras + KF5::Activities + KF5::ActivitiesStats + KF5::ConfigCore + KF5::CoreAddons + KF5::I18n + KF5::ItemModels + KF5::KDELibs4Support # FIXME: New Solid power management API doesn't exist yet, so we need to use deprecated stuff. + KF5::KIOCore + KF5::KIOWidgets + KF5::KIOFileWidgets + KF5::People + KF5::PeopleWidgets + KF5::PlasmaQuick + KF5::Runner + KF5::Service + KF5::Solid + KF5::WindowSystem + PW::KWorkspace) + +if (${HAVE_APPSTREAMQT}) +target_link_libraries(kickerplugin AppStreamQt) +endif() + +install(TARGETS kickerplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/kicker) diff --git a/applets/kicker/Messages.sh b/applets/kicker/Messages.sh new file mode 100644 --- /dev/null +++ b/applets/kicker/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.cpp` -o $podir/libkicker.pot diff --git a/applets/kicker/plugin/abstractentry.h b/applets/kicker/plugin/abstractentry.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/abstractentry.h @@ -0,0 +1,79 @@ +/*************************************************************************** + * Copyright (C) 2012 Aurélien Gâteau * + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef ABSTRACTENTRY_H +#define ABSTRACTENTRY_H + +#include "abstractmodel.h" + +#include +#include + +class AbstractEntry +{ + public: + explicit AbstractEntry(AbstractModel *owner); + virtual ~AbstractEntry(); + + enum EntryType { RunnableType, GroupType, SeparatorType }; + + virtual EntryType type() const = 0; + + AbstractModel *owner() const; + + virtual bool isValid() const; + + virtual QIcon icon() const; + virtual QString name() const; + virtual QString group() const; + virtual QString description() const; + + virtual QString id() const; + virtual QUrl url() const; + + virtual bool hasChildren() const; + virtual AbstractModel *childModel() const; + + virtual bool hasActions() const; + virtual QVariantList actions() const; + + virtual bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()); + + protected: + AbstractModel* m_owner; +}; + +class AbstractGroupEntry : public AbstractEntry +{ + public: + explicit AbstractGroupEntry(AbstractModel *owner); + + EntryType type() const override { return GroupType; } +}; + +class SeparatorEntry : public AbstractEntry +{ + public: + explicit SeparatorEntry(AbstractModel *owner); + + EntryType type() const override { return SeparatorType; } +}; + +#endif diff --git a/dataengines/share/shareservice.h b/applets/kicker/plugin/abstractentry.cpp rename from dataengines/share/shareservice.h rename to applets/kicker/plugin/abstractentry.cpp --- a/dataengines/share/shareservice.h +++ b/applets/kicker/plugin/abstractentry.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2014-2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,54 +17,92 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_SERVICE_H -#define SHARE_SERVICE_H +#include "abstractentry.h" -#include "shareengine.h" +#include -#include -#include -#include +AbstractEntry::AbstractEntry(AbstractModel *owner) +: m_owner(owner) +{ +} + +AbstractEntry::~AbstractEntry() +{ +} + +AbstractModel *AbstractEntry::owner() const +{ + return m_owner; +} + +bool AbstractEntry::isValid() const +{ + return true; +} + +QIcon AbstractEntry::icon() const +{ + return QIcon(); +} + +QString AbstractEntry::name() const +{ + return QString(); +} + + +QString AbstractEntry::group() const +{ + return QString(); +} -class ShareProvider; +QString AbstractEntry::description() const +{ + return QString(); +} -namespace Plasma { - class ServiceJob; +QString AbstractEntry::id() const +{ + return QString(); } -namespace KJSEmbed { - class Engine; +QUrl AbstractEntry::url() const +{ + return QUrl(); } -class ShareService : public Plasma::Service +bool AbstractEntry::hasChildren() const { - Q_OBJECT + return false; +} -public: - explicit ShareService(ShareEngine *engine); - Plasma::ServiceJob *createJob(const QString &operation, - QMap ¶meters) override; -}; +AbstractModel *AbstractEntry::childModel() const +{ + return nullptr; +} -class ShareJob : public Plasma::ServiceJob +bool AbstractEntry::hasActions() const { - Q_OBJECT + return false; +} -public: - ShareJob(const QString &destination, const QString &operation, - QMap ¶meters, QObject *parent = nullptr); - ~ShareJob() override; - void start() override; +QVariantList AbstractEntry::actions() const +{ + return QVariantList(); +} -public Q_SLOTS: - void publish(); - void showResult(const QString &url); - void showError(const QString &msg); +bool AbstractEntry::run(const QString& actionId, const QVariant &argument) +{ + Q_UNUSED(actionId) + Q_UNUSED(argument) -private: - QScopedPointer m_engine; - ShareProvider *m_provider; - KPackage::Package m_package; -}; + return false; +} -#endif // SHARE_SERVICE +AbstractGroupEntry::AbstractGroupEntry(AbstractModel *owner) : AbstractEntry(owner) +{ +} + +SeparatorEntry::SeparatorEntry(AbstractModel *owner) : AbstractEntry(owner) +{ +} diff --git a/applets/kicker/plugin/abstractmodel.h b/applets/kicker/plugin/abstractmodel.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/abstractmodel.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef ABSTRACTMODEL_H +#define ABSTRACTMODEL_H + +#include + +class AbstractEntry; + +class AbstractModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) + + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(int separatorCount READ separatorCount NOTIFY separatorCountChanged) + Q_PROPERTY(int iconSize READ iconSize WRITE setIconSize NOTIFY iconSizeChanged) + Q_PROPERTY(AbstractModel* favoritesModel READ favoritesModel WRITE setFavoritesModel NOTIFY favoritesModelChanged) + + public: + explicit AbstractModel(QObject *parent = nullptr); + ~AbstractModel() override; + + QHash roleNames() const override; + + virtual QString description() const = 0; + + int count() const; + virtual int separatorCount() const; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + int iconSize() const; + void setIconSize(int size); + + Q_INVOKABLE virtual bool trigger(int row, const QString &actionId, const QVariant &argument) = 0; + + Q_INVOKABLE virtual void refresh(); + + Q_INVOKABLE virtual QString labelForRow(int row); + + Q_INVOKABLE virtual AbstractModel *modelForRow(int row); + Q_INVOKABLE virtual int rowForModel(AbstractModel *model); + + virtual bool hasActions() const; + virtual QVariantList actions() const; + + virtual AbstractModel* favoritesModel(); + virtual void setFavoritesModel(AbstractModel *model); + AbstractModel* rootModel(); + + virtual void entryChanged(AbstractEntry *entry); + + Q_SIGNALS: + void descriptionChanged() const; + void countChanged() const; + void separatorCountChanged() const; + void iconSizeChanged() const; + void favoritesModelChanged() const; + + protected: + AbstractModel *m_favoritesModel; + + private: + int m_iconSize; +}; + +#endif diff --git a/applets/kicker/plugin/abstractmodel.cpp b/applets/kicker/plugin/abstractmodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/abstractmodel.cpp @@ -0,0 +1,164 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "abstractmodel.h" +#include "actionlist.h" + +AbstractModel::AbstractModel(QObject *parent) : QAbstractListModel(parent) +, m_favoritesModel(nullptr) +, m_iconSize(32) +{ +} + +AbstractModel::~AbstractModel() +{ +} + +QHash AbstractModel::roleNames() const +{ + QHash roles; + roles.insert(Qt::DisplayRole, "display"); + roles.insert(Qt::DecorationRole, "decoration"); + roles.insert(Kicker::GroupRole, "group"); + roles.insert(Kicker::DescriptionRole, "description"); + roles.insert(Kicker::FavoriteIdRole, "favoriteId"); + roles.insert(Kicker::IsParentRole, "isParent"); + roles.insert(Kicker::IsSeparatorRole, "isSeparator"); + roles.insert(Kicker::HasChildrenRole, "hasChildren"); + roles.insert(Kicker::HasActionListRole, "hasActionList"); + roles.insert(Kicker::ActionListRole, "actionList"); + roles.insert(Kicker::UrlRole, "url"); + + return roles; +} + +int AbstractModel::count() const +{ + return rowCount(); +} + +int AbstractModel::separatorCount() const +{ + return 0; +} + +int AbstractModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return 1; +} + +int AbstractModel::iconSize() const +{ + return m_iconSize; +} + +void AbstractModel::setIconSize(int iconSize) { + if (m_iconSize != iconSize) { + m_iconSize = iconSize; + refresh(); + } +} + +void AbstractModel::refresh() +{ +} + +QString AbstractModel::labelForRow(int row) +{ + return data(index(row, 0), Qt::DisplayRole).toString(); +} + +AbstractModel *AbstractModel::modelForRow(int row) +{ + Q_UNUSED(row) + + return nullptr; +} + +int AbstractModel::rowForModel(AbstractModel *model) +{ + Q_UNUSED(model) + + return -1; +} + +bool AbstractModel::hasActions() const +{ + return false; +} + +QVariantList AbstractModel::actions() const +{ + return QVariantList(); +} + +AbstractModel* AbstractModel::favoritesModel() +{ + if (m_favoritesModel) { + return m_favoritesModel; + } else { + AbstractModel *model = rootModel(); + + if (model && model != this) { + return model->favoritesModel(); + } + } + + return nullptr; +} + +void AbstractModel::setFavoritesModel(AbstractModel *model) +{ + if (m_favoritesModel != model) { + m_favoritesModel = model; + + emit favoritesModelChanged(); + } +} + +AbstractModel* AbstractModel::rootModel() +{ + if (!parent()) { + return nullptr; + } + + QObject *p = this; + AbstractModel *rootModel = nullptr; + + while (p) { + if (qobject_cast(p)) { + rootModel = qobject_cast(p); + } else { + return rootModel; + } + + p = p->parent(); + } + + return rootModel; +} + +void AbstractModel::entryChanged(AbstractEntry *entry) +{ + Q_UNUSED(entry) +} diff --git a/applets/kicker/plugin/actionlist.h b/applets/kicker/plugin/actionlist.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/actionlist.h @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (C) 2013 by Aurélien Gâteau * + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef ACTIONLIST_H +#define ACTIONLIST_H + +#include + +#include + +class KFileItem; + +namespace Kicker +{ + +enum { + DescriptionRole = Qt::UserRole + 1, + GroupRole, + FavoriteIdRole, + IsSeparatorRole, + IsDropPlaceholderRole, + IsParentRole, + HasChildrenRole, + HasActionListRole, + ActionListRole, + UrlRole +}; + +QVariantMap createActionItem(const QString &label, const QString &icon, const QString &actionId, const QVariant &argument = QVariant()); + +QVariantMap createTitleActionItem(const QString &label); + +QVariantMap createSeparatorActionItem(); + +QVariantList createActionListForFileItem(const KFileItem &fileItem); +bool handleFileItemAction(const KFileItem &fileItem, const QString &actionId, const QVariant &argument, bool *close); + +QVariantList createAddLauncherActionList(QObject *appletInterface, const KService::Ptr &service); +bool handleAddLauncherAction(const QString &actionId, QObject *appletInterface, const KService::Ptr &service); + +QVariantList jumpListActions(KService::Ptr service); + +QVariantList recentDocumentActions(KService::Ptr service); +bool handleRecentDocumentAction(KService::Ptr service, const QString &actionId, const QVariant &argument); + +bool canEditApplication(const QString &entryPath); +void editApplication(const QString &entryPath, const QString &menuId); +QVariantList editApplicationAction(const KService::Ptr &service); +bool handleEditApplicationAction(const QString &actionId, const KService::Ptr &service); + +QVariantList appstreamActions(const KService::Ptr &service); +bool handleAppstreamActions(const QString &actionId, const QVariant &argument); + +QString resolvedServiceEntryPath(const KService::Ptr &service); + +} + +#endif diff --git a/applets/kicker/plugin/actionlist.cpp b/applets/kicker/plugin/actionlist.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/actionlist.cpp @@ -0,0 +1,415 @@ +/*************************************************************************** + * Copyright (C) 2013 by Aurélien Gâteau * + * Copyright (C) 2014 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "actionlist.h" +#include "menuentryeditor.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "containmentinterface.h" + +#ifdef HAVE_APPSTREAMQT +#include +#endif + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +namespace Kicker +{ + +QVariantMap createActionItem(const QString &label, const QString &icon, const QString &actionId, const QVariant &argument) +{ + QVariantMap map; + + map[QStringLiteral("text")] = label; + map[QStringLiteral("icon")] = icon; + map[QStringLiteral("actionId")] = actionId; + + if (argument.isValid()) { + map[QStringLiteral("actionArgument")] = argument; + } + + return map; +} + +QVariantMap createTitleActionItem(const QString &label) +{ + QVariantMap map; + + map[QStringLiteral("text")] = label; + map[QStringLiteral("type")] = QStringLiteral("title"); + + return map; +} + +QVariantMap createSeparatorActionItem() +{ + QVariantMap map; + + map[QStringLiteral("type")] = QStringLiteral("separator"); + + return map; +} + +QVariantList createActionListForFileItem(const KFileItem &fileItem) +{ + QVariantList list; + + KService::List services = KMimeTypeTrader::self()->query(fileItem.mimetype(), QStringLiteral("Application")); + + if (!services.isEmpty()) { + list << createTitleActionItem(i18n("Open with:")); + + foreach (const KService::Ptr service, services) { + const QString text = service->name().replace(QLatin1Char('&'), QStringLiteral("&&")); + QVariantMap item = createActionItem(text, service->icon(), QStringLiteral("_kicker_fileItem_openWith"), service->entryPath()); + + list << item; + } + + list << createSeparatorActionItem(); + } + + const QVariantMap &propertiesItem = createActionItem(i18n("Properties"), QStringLiteral("document-properties"), QStringLiteral("_kicker_fileItem_properties")); + list << propertiesItem; + + return list; +} + +bool handleFileItemAction(const KFileItem &fileItem, const QString &actionId, const QVariant &argument, bool *close) +{ + if (actionId == QLatin1String("_kicker_fileItem_properties")) { + KPropertiesDialog *dlg = new KPropertiesDialog(fileItem, QApplication::activeWindow()); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->show(); + + *close = false; + + return true; + } + + if (actionId == QLatin1String("_kicker_fileItem_openWith")) { + const QString path = argument.toString(); + const KService::Ptr service = KService::serviceByDesktopPath(path); + + if (!service) { + return false; + } + + KRun::runService(*service, QList() << fileItem.url(), QApplication::activeWindow()); + + *close = true; + + return true; + } + + return false; +} + +QVariantList createAddLauncherActionList(QObject *appletInterface, const KService::Ptr &service) +{ + QVariantList actionList; + if (!service) { + return actionList; + } + + if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Desktop)) { + QVariantMap addToDesktopAction = Kicker::createActionItem(i18n("Add to Desktop"), QStringLiteral("list-add"), QStringLiteral("addToDesktop")); + actionList << addToDesktopAction; + } + + if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Panel)) { + QVariantMap addToPanelAction = Kicker::createActionItem(i18n("Add to Panel (Widget)"), QStringLiteral("list-add"), QStringLiteral("addToPanel")); + actionList << addToPanelAction; + } + + if (service && ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::TaskManager, Kicker::resolvedServiceEntryPath(service))) { + QVariantMap addToTaskManagerAction = Kicker::createActionItem(i18n("Pin to Task Manager"), QStringLiteral("pin"), QStringLiteral("addToTaskManager")); + actionList << addToTaskManagerAction; + } + + return actionList; +} + +bool handleAddLauncherAction(const QString &actionId, QObject *appletInterface, const KService::Ptr &service) +{ + if (!service) { + return false; + } + + if (actionId == QLatin1String("addToDesktop")) { + if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Desktop)) { + ContainmentInterface::addLauncher(appletInterface, ContainmentInterface::Desktop, Kicker::resolvedServiceEntryPath(service)); + } + return true; + } else if (actionId == QLatin1String("addToPanel")) { + if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Panel)) { + ContainmentInterface::addLauncher(appletInterface, ContainmentInterface::Panel, Kicker::resolvedServiceEntryPath(service)); + } + return true; + } else if (actionId == QLatin1String("addToTaskManager")) { + if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::TaskManager, Kicker::resolvedServiceEntryPath(service))) { + ContainmentInterface::addLauncher(appletInterface, ContainmentInterface::TaskManager, Kicker::resolvedServiceEntryPath(service)); + } + return true; + } + + return false; +} + +QString storageIdFromService(KService::Ptr service) +{ + QString storageId = service->storageId(); + + if (storageId.endsWith(QLatin1String(".desktop"))) { + storageId = storageId.left(storageId.length() - 8); + } + + return storageId; +} + +QVariantList jumpListActions(KService::Ptr service) +{ + QVariantList list; + + if (!service) { + return list; + } + + const auto &actions = service->actions(); + foreach (const KServiceAction &action, actions) { + if (action.text().isEmpty() || action.exec().isEmpty()) { + continue; + } + + QVariantMap item = createActionItem(action.text(), action.icon(), QStringLiteral("_kicker_jumpListAction"), action.exec()); + + list << item; + } + + return list; +} + +QVariantList recentDocumentActions(KService::Ptr service) +{ + QVariantList list; + + if (!service) { + return list; + } + + const QString storageId = storageIdFromService(service); + + if (storageId.isEmpty()) { + return list; + } + + auto query = UsedResources + | RecentlyUsedFirst + | Agent(storageId) + | Type::any() + | Activity::current() + | Url::file(); + + ResultSet results(query); + + ResultSet::const_iterator resultIt; + resultIt = results.begin(); + + while (list.count() < 6 && resultIt != results.end()) { + const QString resource = (*resultIt).resource(); + ++resultIt; + + const QUrl url(resource); + + if (!url.isValid()) { + continue; + } + + const KFileItem fileItem(url); + + if (!fileItem.isFile()) { + continue; + } + + if (list.isEmpty()) { + list << createTitleActionItem(i18n("Recent Documents")); + } + + QVariantMap item = createActionItem(url.fileName(), fileItem.iconName(), QStringLiteral("_kicker_recentDocument"), resource); + + list << item; + } + + if (!list.isEmpty()) { + QVariantMap forgetAction = createActionItem(i18n("Forget Recent Documents"), QStringLiteral("edit-clear-history"), QStringLiteral("_kicker_forgetRecentDocuments")); + list << forgetAction; + } + + return list; +} + +bool handleRecentDocumentAction(KService::Ptr service, const QString &actionId, const QVariant &_argument) +{ + if (!service) { + return false; + } + + if (actionId == QLatin1String("_kicker_forgetRecentDocuments")) { + const QString storageId = storageIdFromService(service); + + if (storageId.isEmpty()) { + return false; + } + + auto query = UsedResources + | Agent(storageId) + | Type::any() + | Activity::current() + | Url::file(); + + KAStats::forgetResources(query); + + return false; + } + + QString argument = _argument.toString(); + + if (argument.isEmpty()) { + return false; + } + + return (KRun::runService(*service, QList() << QUrl(argument), QApplication::activeWindow()) != 0); +} + +Q_GLOBAL_STATIC(MenuEntryEditor, menuEntryEditor) + +bool canEditApplication(const KService::Ptr &service) +{ + return (service->isApplication() && menuEntryEditor->canEdit(service->entryPath())); +} + +void editApplication(const QString &entryPath, const QString &menuId) +{ + menuEntryEditor->edit(entryPath, menuId); +} + +QVariantList editApplicationAction(const KService::Ptr &service) +{ + QVariantList actionList; + + if (canEditApplication(service)) { + // TODO: Using the KMenuEdit icon might be misleading. + QVariantMap editAction = Kicker::createActionItem(i18n("Edit Application..."), QStringLiteral("kmenuedit"), QStringLiteral("editApplication")); + actionList << editAction; + } + + return actionList; +} + +bool handleEditApplicationAction(const QString &actionId, const KService::Ptr &service) +{ + + if (service && actionId ==QLatin1String("editApplication") && canEditApplication(service)) { + Kicker::editApplication(service->entryPath(), service->menuId()); + + return true; + } + + return false; +} + +#ifdef HAVE_APPSTREAMQT +Q_GLOBAL_STATIC(AppStream::Pool, appstreamPool) +#endif + +QVariantList appstreamActions(const KService::Ptr &service) +{ + QVariantList ret; + +#ifdef HAVE_APPSTREAMQT + const KService::Ptr appStreamHandler = KMimeTypeTrader::self()->preferredService(QStringLiteral("x-scheme-handler/appstream")); + + // Don't show action if we can't find any app to handle appstream:// URLs. + if (!appStreamHandler) { + if (!KProtocolInfo::isHelperProtocol(QStringLiteral("appstream")) + || KProtocolInfo::exec(QStringLiteral("appstream")).isEmpty()) { + return ret; + } + } + + if (!appstreamPool.exists()) { + appstreamPool->load(); + } + + const auto components = appstreamPool->componentsById(service->desktopEntryName()+QLatin1String(".desktop")); + for(const auto &component: components) { + const QString componentId = component.id(); + + QVariantMap appstreamAction = Kicker::createActionItem( + i18nc("@action opens a software center with the application", "Uninstall or Manage Add-Ons..."), + appStreamHandler->icon(), + "manageApplication", QVariant(QLatin1String("appstream://") + componentId)); + ret << appstreamAction; + } +#else + Q_UNUSED(service) +#endif + + return ret; +} + +bool handleAppstreamActions(const QString &actionId, const QVariant &argument) +{ + if (actionId == QLatin1String("manageApplication")) { + return QDesktopServices::openUrl(QUrl(argument.toString())); + } + + return false; +} + +QString resolvedServiceEntryPath(const KService::Ptr &service) +{ + QString path = service->entryPath(); + if (!QDir::isAbsolutePath(path)) { + path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + path); + } + return path; +} + +} diff --git a/applets/kicker/plugin/appentry.h b/applets/kicker/plugin/appentry.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/appentry.h @@ -0,0 +1,95 @@ +/*************************************************************************** + * Copyright (C) 201 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef APPENTRY_H +#define APPENTRY_H + +#include "abstractentry.h" + +#include +#include + +class AppsModel; +class MenuEntryEditor; + +class AppEntry : public AbstractEntry +{ + public: + enum NameFormat { + NameOnly = 0, + GenericNameOnly, + NameAndGenericName, + GenericNameAndName + }; + + explicit AppEntry(AbstractModel *owner, KService::Ptr service, NameFormat nameFormat); + explicit AppEntry(AbstractModel *owner, const QString &id); + + EntryType type() const override { return RunnableType; } + + bool isValid() const override; + + QIcon icon() const override; + QString name() const override; + QString description() const override; + KService::Ptr service() const; + + QString id() const override; + QUrl url() const override; + + bool hasActions() const override; + QVariantList actions() const override; + + bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()) override; + + QString menuId() const; + + static QString nameFromService(const KService::Ptr service, NameFormat nameFormat); + static KService::Ptr defaultAppByName(const QString &name); + + private: + void init(NameFormat nameFormat); + + QString m_id; + QString m_name; + QString m_description; + mutable QIcon m_icon; + KService::Ptr m_service; + static MenuEntryEditor *m_menuEntryEditor; +}; + +class AppGroupEntry : public AbstractGroupEntry +{ + public: + AppGroupEntry(AppsModel *parentModel, KServiceGroup::Ptr group, + bool paginate, int pageSize, bool flat, bool sorted, bool separators, int appNameFormat); + + QIcon icon() const override; + QString name() const override; + + bool hasChildren() const override; + AbstractModel *childModel() const override; + + private: + KServiceGroup::Ptr m_group; + mutable QIcon m_icon; + QPointer m_childModel; +}; + +#endif diff --git a/applets/kicker/plugin/appentry.cpp b/applets/kicker/plugin/appentry.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/appentry.cpp @@ -0,0 +1,307 @@ +/*************************************************************************** + * Copyright (C) 201 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include +#include "appentry.h" +#include "actionlist.h" +#include "appsmodel.h" +#include "containmentinterface.h" + +#include + +#include +#include +#include +#if HAVE_X11 +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +AppEntry::AppEntry(AbstractModel *owner, KService::Ptr service, NameFormat nameFormat) +: AbstractEntry(owner) +, m_service(service) +{ + if (m_service) { + init(nameFormat); + } +} + +AppEntry::AppEntry(AbstractModel *owner, const QString &id) : AbstractEntry(owner) +{ + const QUrl url(id); + + if (url.scheme() == QLatin1String("preferred")) { + m_service = defaultAppByName(url.host()); + m_id = id; + } else { + m_service = KService::serviceByStorageId(id); + } + + if (m_service) { + init((NameFormat)owner->rootModel()->property("appNameFormat").toInt()); + } +} + +void AppEntry::init(NameFormat nameFormat) +{ + m_name = nameFromService(m_service, nameFormat); + + if (nameFormat == GenericNameOnly) { + m_description = nameFromService(m_service, NameOnly); + } else { + m_description = nameFromService(m_service, GenericNameOnly); + } +} + +bool AppEntry::isValid() const +{ + return m_service; +} + +QIcon AppEntry::icon() const +{ + if (m_icon.isNull()) { + m_icon = QIcon::fromTheme(m_service->icon(), QIcon::fromTheme(QStringLiteral("unknown"))); + } + return m_icon; +} + +QString AppEntry::name() const +{ + return m_name; +} + +QString AppEntry::description() const +{ + return m_description; +} + +KService::Ptr AppEntry::service() const +{ + return m_service; +} + +QString AppEntry::id() const +{ + if (!m_id.isEmpty()) { + return m_id; + } + + return m_service->storageId(); +} + +QString AppEntry::menuId() const +{ + return m_service->menuId(); +} + +QUrl AppEntry::url() const +{ + return QUrl::fromLocalFile(Kicker::resolvedServiceEntryPath(m_service)); +} + +bool AppEntry::hasActions() const +{ + return true; +} + +QVariantList AppEntry::actions() const +{ + QVariantList actionList; + + actionList << Kicker::jumpListActions(m_service); + if (!actionList.isEmpty()) { + actionList << Kicker::createSeparatorActionItem(); + } + + QObject *appletInterface = m_owner->rootModel()->property("appletInterface").value(); + + bool systemImmutable = false; + if (appletInterface) { + systemImmutable = (appletInterface->property("immutability").toInt() == Plasma::Types::SystemImmutable); + } + + const QVariantList &addLauncherActions = Kicker::createAddLauncherActionList(appletInterface, m_service); + if (!systemImmutable && !addLauncherActions.isEmpty()) { + actionList << addLauncherActions + << Kicker::createSeparatorActionItem(); + } + + const QVariantList &recentDocuments = Kicker::recentDocumentActions(m_service); + if (!recentDocuments.isEmpty()) { + actionList << recentDocuments << Kicker::createSeparatorActionItem(); + } + + // Don't allow adding launchers, editing, hiding, or uninstalling applications + // when system is immutable. + if (systemImmutable) { + return actionList; + } + + if (m_service->isApplication()) { + actionList << Kicker::createSeparatorActionItem(); + actionList << Kicker::editApplicationAction(m_service); + actionList << Kicker::appstreamActions(m_service); + } + + if (appletInterface) { + QQmlPropertyMap *appletConfig = qobject_cast(appletInterface->property("configuration").value()); + + if (appletConfig && appletConfig->contains(QLatin1String("hiddenApplications")) && qobject_cast(m_owner)) { + const QStringList &hiddenApps = appletConfig->value(QLatin1String("hiddenApplications")).toStringList(); + + if (!hiddenApps.contains(m_service->menuId())) { + QVariantMap hideAction = Kicker::createActionItem(i18n("Hide Application"), QStringLiteral("view-hidden"), QStringLiteral("hideApplication")); + actionList << hideAction; + } + } + } + + return actionList; +} + +bool AppEntry::run(const QString& actionId, const QVariant &argument) +{ + if (!m_service->isValid()) { + return false; + } + + if (actionId.isEmpty()) { + quint32 timeStamp = 0; + +#if HAVE_X11 + if (QX11Info::isPlatformX11()) { + timeStamp = QX11Info::appUserTime(); + } +#endif + + KRun::runApplication(*m_service, {}, nullptr, KRun::DeleteTemporaryFiles, {}, KStartupInfo::createNewStartupIdForTimestamp(timeStamp)); + + KActivities::ResourceInstance::notifyAccessed(QUrl(QStringLiteral("applications:") + m_service->storageId()), + QStringLiteral("org.kde.plasma.kicker")); + + return true; + } + + QObject *appletInterface = m_owner->rootModel()->property("appletInterface").value(); + + if (Kicker::handleAddLauncherAction(actionId, appletInterface, m_service)) { + return true; + } else if (Kicker::handleEditApplicationAction(actionId, m_service)) { + return true; + } else if (Kicker::handleAppstreamActions(actionId, argument)) { + return true; + } else if (actionId == QLatin1String("_kicker_jumpListAction")) { + return KRun::run(argument.toString(), {}, nullptr, m_service->name(), m_service->icon()); + } + + return Kicker::handleRecentDocumentAction(m_service, actionId, argument); +} + +QString AppEntry::nameFromService(const KService::Ptr service, NameFormat nameFormat) +{ + const QString &name = service->name(); + QString genericName = service->genericName(); + + if (genericName.isEmpty()) { + genericName = service->comment(); + } + + if (nameFormat == NameOnly || genericName.isEmpty() || name == genericName) { + return name; + } else if (nameFormat == GenericNameOnly) { + return genericName; + } else if (nameFormat == NameAndGenericName) { + return i18nc("App name (Generic name)", "%1 (%2)", name, genericName); + } else { + return i18nc("Generic name (App name)", "%1 (%2)", genericName, name); + } +} + +KService::Ptr AppEntry::defaultAppByName(const QString& name) +{ + if (name == QLatin1String("browser")) { + KConfigGroup config(KSharedConfig::openConfig(), "General"); + QString browser = config.readPathEntry("BrowserApplication", QString()); + + if (browser.isEmpty()) { + return KMimeTypeTrader::self()->preferredService(QLatin1String("text/html")); + } else if (browser.startsWith(QLatin1Char('!'))) { + browser.remove(0, 1); + } + + return KService::serviceByStorageId(browser); + } + + return KService::Ptr(); +} + +AppGroupEntry::AppGroupEntry(AppsModel *parentModel, KServiceGroup::Ptr group, + bool paginate, int pageSize, bool flat, bool sorted, bool separators, int appNameFormat) : AbstractGroupEntry(parentModel), + m_group(group) +{ + AppsModel* model = new AppsModel(group->entryPath(), paginate, pageSize, flat, + sorted, separators, parentModel); + model->setAppNameFormat(appNameFormat); + m_childModel = model; + + QObject::connect(parentModel, &AppsModel::cleared, model, &AppsModel::deleteLater); + + QObject::connect(model, &AppsModel::countChanged, + [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } + ); + + QObject::connect(model, &AppsModel::hiddenEntriesChanged, + [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } + ); +} + +QIcon AppGroupEntry::icon() const +{ + if (m_icon.isNull()) { + m_icon = QIcon::fromTheme(m_group->icon(), QIcon::fromTheme(QStringLiteral("unknown"))); + } + return m_icon; +} + +QString AppGroupEntry::name() const +{ + return m_group->caption(); +} + +bool AppGroupEntry::hasChildren() const +{ + return m_childModel && m_childModel->count() > 0; +} + +AbstractModel *AppGroupEntry::childModel() const { + return m_childModel; +} diff --git a/applets/kicker/plugin/appsmodel.h b/applets/kicker/plugin/appsmodel.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/appsmodel.h @@ -0,0 +1,158 @@ +/*************************************************************************** + * Copyright (C) 2012 Aurélien Gâteau * + * Copyright (C) 2013-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef APPSMODEL_H +#define APPSMODEL_H + +#include "abstractmodel.h" +#include "appentry.h" + +#include + +#include + + +class QTimer; + +class AppsModel : public AbstractModel, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(bool autoPopulate READ autoPopulate WRITE setAutoPopulate NOTIFY autoPopulateChanged) + + Q_PROPERTY(bool paginate READ paginate WRITE setPaginate NOTIFY paginateChanged) + Q_PROPERTY(int pageSize READ pageSize WRITE setPageSize NOTIFY pageSizeChanged) + Q_PROPERTY(bool flat READ flat WRITE setFlat NOTIFY flatChanged) + Q_PROPERTY(bool sorted READ sorted WRITE setSorted NOTIFY sortedChanged) + Q_PROPERTY(bool showSeparators READ showSeparators WRITE setShowSeparators NOTIFY showSeparatorsChanged) + Q_PROPERTY(bool showTopLevelItems READ showTopLevelItems WRITE setShowTopLevelItems NOTIFY showTopLevelItemsChanged) + Q_PROPERTY(int appNameFormat READ appNameFormat WRITE setAppNameFormat NOTIFY appNameFormatChanged) + Q_PROPERTY(QObject* appletInterface READ appletInterface WRITE setAppletInterface NOTIFY appletInterfaceChanged) + + public: + explicit AppsModel(const QString &entryPath = QString(), bool paginate = false, int pageSize = 24, + bool flat = false, bool sorted = true, bool separators = true, QObject *parent = nullptr); + explicit AppsModel(const QList entryList, bool deleteEntriesOnDestruction, QObject *parent = nullptr); + ~AppsModel() override; + + QString description() const override; + void setDescription(const QString &text); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + bool autoPopulate() const; + void setAutoPopulate(bool populate); + + Q_INVOKABLE AbstractModel *modelForRow(int row) override; + Q_INVOKABLE int rowForModel(AbstractModel *model) override; + + int separatorCount() const override; + + bool paginate() const; + void setPaginate(bool paginate); + + int pageSize() const; + void setPageSize(int size); + + bool flat() const; + void setFlat(bool flat); + + bool sorted() const; + void setSorted(bool sorted); + + bool showSeparators() const; + void setShowSeparators(bool showSeparators); + + bool showTopLevelItems() const; + void setShowTopLevelItems(bool showTopLevelItems); + + int appNameFormat() const; + void setAppNameFormat(int format); + + QObject *appletInterface() const; + void setAppletInterface(QObject *appletInterface); + + QStringList hiddenEntries() const; + + void entryChanged(AbstractEntry *entry) override; + + void classBegin() override; + void componentComplete() override; + + Q_SIGNALS: + void cleared() const; + void autoPopulateChanged() const; + void paginateChanged() const; + void pageSizeChanged() const; + void flatChanged() const; + void sortedChanged() const; + void showSeparatorsChanged() const; + void showTopLevelItemsChanged() const; + void appNameFormatChanged() const; + void appletInterfaceChanged() const; + void hiddenEntriesChanged() const; + + protected Q_SLOTS: + void refresh() override; + + protected: + void refreshInternal(); + + bool m_complete; + + bool m_paginate; + int m_pageSize; + + QList m_entryList; + bool m_deleteEntriesOnDestruction; + int m_separatorCount; + bool m_showSeparators; + bool m_showTopLevelItems; + + QObject *m_appletInterface; + + private Q_SLOTS: + void checkSycocaChanges(const QStringList &changes); + + private: + void processServiceGroup(KServiceGroup::Ptr group); + void sortEntries(); + + bool m_autoPopulate; + + QString m_description; + QString m_entryPath; + bool m_staticEntryList; + QTimer *m_changeTimer; + bool m_flat; + bool m_sorted; + AppEntry::NameFormat m_appNameFormat; + QStringList m_hiddenEntries; + static MenuEntryEditor *m_menuEntryEditor; +}; + +#endif diff --git a/applets/kicker/plugin/appsmodel.cpp b/applets/kicker/plugin/appsmodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/appsmodel.cpp @@ -0,0 +1,746 @@ +/*************************************************************************** + * Copyright (C) 2012 Aurélien Gâteau * + * Copyright (C) 2013-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "appsmodel.h" +#include "actionlist.h" +#include "rootmodel.h" + +#include +#include +#include +#include + +#include +#include + +AppsModel::AppsModel(const QString &entryPath, bool paginate, int pageSize, bool flat, + bool sorted, bool separators, QObject *parent) +: AbstractModel(parent) +, m_complete(false) +, m_paginate(paginate) +, m_pageSize(pageSize) +, m_deleteEntriesOnDestruction(true) +, m_separatorCount(0) +, m_showSeparators(separators) +, m_showTopLevelItems(false) +, m_appletInterface(nullptr) +, m_autoPopulate(true) +, m_description(i18n("Applications")) +, m_entryPath(entryPath) +, m_staticEntryList(false) +, m_changeTimer(nullptr) +, m_flat(flat) +, m_sorted(sorted) +, m_appNameFormat(AppEntry::NameOnly) +{ + if (!m_entryPath.isEmpty()) { + componentComplete(); + } +} + +AppsModel::AppsModel(const QList entryList, bool deleteEntriesOnDestruction, QObject *parent) +: AbstractModel(parent) +, m_complete(false) +, m_paginate(false) +, m_pageSize(24) +, m_deleteEntriesOnDestruction(deleteEntriesOnDestruction) +, m_separatorCount(0) +, m_showSeparators(false) +, m_showTopLevelItems(false) +, m_appletInterface(nullptr) +, m_autoPopulate(true) +, m_description(i18n("Applications")) +, m_entryPath(QString()) +, m_staticEntryList(true) +, m_changeTimer(nullptr) +, m_flat(true) +, m_sorted(true) +, m_appNameFormat(AppEntry::NameOnly) +{ + foreach(AbstractEntry *suggestedEntry, entryList) { + bool found = false; + + foreach (const AbstractEntry *entry, m_entryList) { + if (entry->type() == AbstractEntry::RunnableType + && static_cast(entry)->service()->storageId() + == static_cast(suggestedEntry)->service()->storageId()) { + found = true; + break; + } + } + + if (!found) { + m_entryList << suggestedEntry; + } + } + + sortEntries(); +} + +AppsModel::~AppsModel() +{ + if (m_deleteEntriesOnDestruction) { + qDeleteAll(m_entryList); + } +} + +bool AppsModel::autoPopulate() const +{ + return m_autoPopulate; +} + +void AppsModel::setAutoPopulate(bool populate) +{ + if (m_autoPopulate != populate) { + m_autoPopulate = populate; + + emit autoPopulateChanged(); + } +} + +QString AppsModel::description() const +{ + return m_description; +} + +void AppsModel::setDescription(const QString &text) +{ + if (m_description != text) { + m_description = text; + + emit descriptionChanged(); + } +} + +QVariant AppsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_entryList.count()) { + return QVariant(); + } + + const AbstractEntry *entry = m_entryList.at(index.row()); + + if (role == Qt::DisplayRole) { + return entry->name(); + } else if (role == Qt::DecorationRole) { + return entry->icon(); + } else if (role == Kicker::DescriptionRole) { + return entry->description(); + } else if (role == Kicker::FavoriteIdRole && entry->type() == AbstractEntry::RunnableType) { + return entry->id(); + } else if (role == Kicker::UrlRole && entry->type() == AbstractEntry::RunnableType) { + return entry->url(); + } else if (role == Kicker::IsParentRole) { + return (entry->type() == AbstractEntry::GroupType); + } else if (role == Kicker::IsSeparatorRole) { + return (entry->type() == AbstractEntry::SeparatorType); + } else if (role == Kicker::HasChildrenRole) { + return entry->hasChildren(); + } else if (role == Kicker::HasActionListRole) { + const AppsModel *appsModel = qobject_cast(entry->childModel()); + + return entry->hasActions() || (appsModel && !appsModel->hiddenEntries().isEmpty()); + } else if (role == Kicker::ActionListRole) { + QVariantList actionList = entry->actions(); + + if (!m_hiddenEntries.isEmpty()) { + actionList << Kicker::createSeparatorActionItem(); + QVariantMap unhideSiblingApplicationsAction = Kicker::createActionItem(i18n("Unhide Applications in this Submenu"), QStringLiteral("view-visible"), QStringLiteral("unhideSiblingApplications")); + actionList << unhideSiblingApplicationsAction; + } + + const AppsModel *appsModel = qobject_cast(entry->childModel()); + + if (appsModel && !appsModel->hiddenEntries().isEmpty()) { + QVariantMap unhideChildApplicationsAction = Kicker::createActionItem(i18n("Unhide Applications in '%1'", entry->name()), QStringLiteral("view-visible"), QStringLiteral("unhideChildApplications")); + actionList << unhideChildApplicationsAction; + } + + return actionList; + } + + return QVariant(); +} + +QModelIndex AppsModel::index(int row, int column, const QModelIndex &parent) const +{ + return hasIndex(row, column, parent) ? createIndex(row, column, m_entryList.at(row)) : QModelIndex(); +} + + +int AppsModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_entryList.count(); +} + +bool AppsModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (row < 0 || row >= m_entryList.count()) { + return false; + } + + AbstractEntry *entry = m_entryList.at(row); + + if (actionId == QLatin1String("hideApplication") && entry->type() == AbstractEntry::RunnableType) { + QObject *appletInterface = rootModel()->property("appletInterface").value(); + QQmlPropertyMap *appletConfig = nullptr; + if (appletInterface) { + appletConfig = qobject_cast(appletInterface->property("configuration").value()); + } + + if (appletConfig && appletConfig->contains(QLatin1String("hiddenApplications"))) { + QStringList hiddenApps = appletConfig->value(QLatin1String("hiddenApplications")).toStringList(); + + KService::Ptr service = static_cast(entry)->service(); + + if (!hiddenApps.contains(service->menuId())) { + hiddenApps << service->menuId(); + + appletConfig->insert(QLatin1String("hiddenApplications"), hiddenApps); + QMetaObject::invokeMethod(appletConfig, "valueChanged", Qt::DirectConnection, + Q_ARG(QString, QStringLiteral("hiddenApplications")), + Q_ARG(QVariant, hiddenApps)); + + refresh(); + + emit hiddenEntriesChanged(); + } + } + + return false; + } else if (actionId == QLatin1String("unhideSiblingApplications")) { + QObject *appletInterface = rootModel()->property("appletInterface").value(); + QQmlPropertyMap *appletConfig = nullptr; + if (appletInterface) { + appletConfig = qobject_cast(appletInterface->property("configuration").value()); + } + + if (appletConfig && appletConfig->contains(QLatin1String("hiddenApplications"))) { + QStringList hiddenApps = appletConfig->value(QLatin1String("hiddenApplications")).toStringList(); + + foreach(const QString& app, m_hiddenEntries) { + hiddenApps.removeOne(app); + } + + appletConfig->insert(QStringLiteral("hiddenApplications"), hiddenApps); + QMetaObject::invokeMethod(appletConfig, "valueChanged", Qt::DirectConnection, + Q_ARG(QString, QStringLiteral("hiddenApplications")), + Q_ARG(QVariant, hiddenApps)); + + m_hiddenEntries.clear(); + + refresh(); + + emit hiddenEntriesChanged(); + } + + return false; + } else if (actionId == QLatin1String("unhideChildApplications")) { + QObject *appletInterface = rootModel()->property("appletInterface").value(); + QQmlPropertyMap *appletConfig = nullptr; + if (appletInterface) { + appletConfig = qobject_cast(appletInterface->property("configuration").value()); + } + + if (entry->type() == AbstractEntry::GroupType + && appletConfig && appletConfig->contains(QLatin1String("hiddenApplications"))) { + + const AppsModel *appsModel = qobject_cast(entry->childModel()); + + if (!appsModel) { + return false; + } + + QStringList hiddenApps = appletConfig->value(QLatin1String("hiddenApplications")).toStringList(); + + foreach(const QString& app, appsModel->hiddenEntries()) { + hiddenApps.removeOne(app); + } + + appletConfig->insert(QStringLiteral("hiddenApplications"), hiddenApps); + QMetaObject::invokeMethod(appletConfig, "valueChanged", Qt::DirectConnection, + Q_ARG(QString, QStringLiteral("hiddenApplications")), + Q_ARG(QVariant, hiddenApps)); + + refresh(); + + emit hiddenEntriesChanged(); + } + + return false; + } + + return entry->run(actionId, argument); +} + +AbstractModel *AppsModel::modelForRow(int row) +{ + if (row < 0 || row >= m_entryList.count()) { + return nullptr; + } + + return m_entryList.at(row)->childModel(); +} + +int AppsModel::rowForModel(AbstractModel *model) +{ + for (int i = 0; i < m_entryList.count(); ++i) { + if (m_entryList.at(i)->childModel() == model) { + return i; + } + } + + return -1; +} + +int AppsModel::separatorCount() const +{ + return m_separatorCount; +} + +bool AppsModel::paginate() const +{ + return m_paginate; +} + +void AppsModel::setPaginate(bool paginate) +{ + if (m_paginate != paginate) { + m_paginate = paginate; + + refresh(); + + emit paginateChanged(); + } +} + +int AppsModel::pageSize() const +{ + return m_pageSize; +} + +void AppsModel::setPageSize(int size) +{ + if (m_pageSize != size) { + m_pageSize = size; + + refresh(); + + emit pageSizeChanged(); + } +} + +bool AppsModel::flat() const +{ + return m_flat; +} + +void AppsModel::setFlat(bool flat) +{ + if (m_flat != flat) { + m_flat = flat; + + refresh(); + + emit flatChanged(); + } +} + +bool AppsModel::sorted() const +{ + return m_sorted; +} + +void AppsModel::setSorted(bool sorted) +{ + if (m_sorted != sorted) { + m_sorted = sorted; + + refresh(); + + emit sortedChanged(); + } +} + +bool AppsModel::showSeparators() const +{ + return m_showSeparators; +} + +void AppsModel::setShowSeparators(bool showSeparators) { + if (m_showSeparators != showSeparators) { + m_showSeparators = showSeparators; + + refresh(); + + emit showSeparatorsChanged(); + } +} + +bool AppsModel::showTopLevelItems() const +{ + return m_showTopLevelItems; +} + +void AppsModel::setShowTopLevelItems(bool showTopLevelItems) { + if (m_showTopLevelItems != showTopLevelItems) { + m_showTopLevelItems = showTopLevelItems; + + refresh(); + + emit showTopLevelItemsChanged(); + } +} + +int AppsModel::appNameFormat() const +{ + return m_appNameFormat; +} + +void AppsModel::setAppNameFormat(int format) +{ + if (m_appNameFormat != (AppEntry::NameFormat)format) { + m_appNameFormat = (AppEntry::NameFormat)format; + + refresh(); + + emit appNameFormatChanged(); + } +} + +QObject* AppsModel::appletInterface() const +{ + return m_appletInterface; +} + +void AppsModel::setAppletInterface(QObject* appletInterface) +{ + if (m_appletInterface != appletInterface) { + m_appletInterface = appletInterface; + + refresh(); + + emit appletInterfaceChanged(); + } +} + +QStringList AppsModel::hiddenEntries() const +{ + return m_hiddenEntries; +} + +void AppsModel::refresh() +{ + if (!m_complete) { + return; + } + + if (m_staticEntryList) { + return; + } + + if (rootModel() == this && !m_appletInterface) { + return; + } + + beginResetModel(); + + refreshInternal(); + + endResetModel(); + + if (favoritesModel()) { + favoritesModel()->refresh(); + } + + emit countChanged(); + emit separatorCountChanged(); +} + +void AppsModel::refreshInternal() +{ + if (m_staticEntryList) { + return; + } + + if (m_entryList.count()) { + qDeleteAll(m_entryList); + m_entryList.clear(); + emit cleared(); + } + + m_hiddenEntries.clear(); + m_separatorCount = 0; + + if (m_entryPath.isEmpty()) { + KServiceGroup::Ptr group = KServiceGroup::root(); + if (!group) { + return; + } + + bool sortByGenericName = (appNameFormat() == AppEntry::GenericNameOnly || appNameFormat() == AppEntry::GenericNameAndName); + + KServiceGroup::List list = group->entries(true /* sorted */, true /* excludeNoDisplay */, + true /* allowSeparators */, sortByGenericName /* sortByGenericName */); + + for (KServiceGroup::List::ConstIterator it = list.constBegin(); it != list.constEnd(); it++) { + const KSycocaEntry::Ptr p = (*it); + + if (p->isType(KST_KServiceGroup)) { + KServiceGroup::Ptr subGroup(static_cast(p.data())); + + if (!subGroup->noDisplay() && subGroup->childCount() > 0) { + AppGroupEntry *groupEntry = new AppGroupEntry(this, subGroup, m_paginate, m_pageSize, m_flat, + m_sorted, m_showSeparators, m_appNameFormat); + m_entryList << groupEntry; + } + } else if (p->isType(KST_KService) && m_showTopLevelItems) { + const KService::Ptr service(static_cast(p.data())); + + if (service->noDisplay()) { + continue; + } + + bool found = false; + + foreach (const AbstractEntry *entry, m_entryList) { + if (entry->type() == AbstractEntry::RunnableType + && static_cast(entry)->service()->storageId() == service->storageId()) { + found = true; + } + } + + if (!found) { + m_entryList << new AppEntry(this, service, m_appNameFormat); + } + } else if (p->isType(KST_KServiceSeparator) && m_showSeparators && m_showTopLevelItems) { + if (!m_entryList.count()) { + continue; + } + + if (m_entryList.last()->type() == AbstractEntry::SeparatorType) { + continue; + } + + m_entryList << new SeparatorEntry(this); + ++m_separatorCount; + } + } + + if (m_entryList.count()) { + while (m_entryList.last()->type() == AbstractEntry::SeparatorType) { + m_entryList.removeLast(); + --m_separatorCount; + } + } + + if (m_sorted) { + sortEntries(); + } + + m_changeTimer = new QTimer(this); + m_changeTimer->setSingleShot(true); + m_changeTimer->setInterval(100); + connect(m_changeTimer, SIGNAL(timeout()), this, SLOT(refresh())); + + connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), SLOT(checkSycocaChanges(QStringList))); + } else { + KServiceGroup::Ptr group = KServiceGroup::group(m_entryPath); + processServiceGroup(group); + + if (m_entryList.count()) { + while (m_entryList.last()->type() == AbstractEntry::SeparatorType) { + m_entryList.removeLast(); + --m_separatorCount; + } + } + + if (m_sorted) { + sortEntries(); + } + + if (m_paginate) { + QList groups; + + int at = 0; + QList page; + + foreach(AbstractEntry *app, m_entryList) { + page.append(app); + + if (at == (m_pageSize - 1)) { + at = 0; + AppsModel *model = new AppsModel(page, true, this); + groups.append(new GroupEntry(this, QString(), QString(), model)); + page.clear(); + } else { + ++at; + } + } + + if (page.count()) { + AppsModel *model = new AppsModel(page, true, this); + groups.append(new GroupEntry(this, QString(), QString(), model)); + } + + m_entryList = groups; + } + } +} + +void AppsModel::processServiceGroup(KServiceGroup::Ptr group) +{ + if (!group || !group->isValid()) { + return; + } + + bool hasSubGroups = false; + + foreach(KServiceGroup::Ptr subGroup, group->groupEntries(KServiceGroup::ExcludeNoDisplay)) { + if (subGroup->childCount() > 0) { + hasSubGroups = true; + + break; + } + } + + bool sortByGenericName = (appNameFormat() == AppEntry::GenericNameOnly || appNameFormat() == AppEntry::GenericNameAndName); + + KServiceGroup::List list = group->entries(true /* sorted */, + true /* excludeNoDisplay */, + (!m_flat || (m_flat && !hasSubGroups)) /* allowSeparators */, + sortByGenericName /* sortByGenericName */); + + QStringList hiddenApps; + + QObject *appletInterface = rootModel()->property("appletInterface").value(); + QQmlPropertyMap *appletConfig = nullptr; + if (appletInterface) { + appletConfig = qobject_cast(appletInterface->property("configuration").value()); + } + if (appletConfig && appletConfig->contains(QLatin1String("hiddenApplications"))) { + hiddenApps = appletConfig->value(QLatin1String("hiddenApplications")).toStringList(); + } + + for (KServiceGroup::List::ConstIterator it = list.constBegin(); + it != list.constEnd(); it++) { + const KSycocaEntry::Ptr p = (*it); + + if (p->isType(KST_KService)) { + const KService::Ptr service(static_cast(p.data())); + + if (service->noDisplay()) { + continue; + } + + if (hiddenApps.contains(service->menuId())) { + m_hiddenEntries << service->menuId(); + + continue; + } + + bool found = false; + + foreach (const AbstractEntry *entry, m_entryList) { + if (entry->type() == AbstractEntry::RunnableType + && static_cast(entry)->service()->storageId() == service->storageId()) { + found = true; + break; + } + } + + if (!found) { + m_entryList << new AppEntry(this, service, m_appNameFormat); + } + } else if (p->isType(KST_KServiceSeparator) && m_showSeparators) { + if (!m_entryList.count()) { + continue; + } + + if (m_entryList.last()->type() == AbstractEntry::SeparatorType) { + continue; + } + + m_entryList << new SeparatorEntry(this); + ++m_separatorCount; + } else if (p->isType(KST_KServiceGroup)) { + const KServiceGroup::Ptr subGroup(static_cast(p.data())); + + if (subGroup->childCount() == 0) { + continue; + } + + if (m_flat) { + m_sorted = true; + const KServiceGroup::Ptr serviceGroup(static_cast(p.data())); + processServiceGroup(serviceGroup); + } else { + AppGroupEntry *groupEntry = new AppGroupEntry(this, subGroup, m_paginate, m_pageSize, m_flat, + m_sorted, m_showSeparators, m_appNameFormat); + m_entryList << groupEntry; + } + } + } +} + +void AppsModel::sortEntries() +{ + QCollator c; + + std::sort(m_entryList.begin(), m_entryList.end(), + [&c](AbstractEntry* a, AbstractEntry* b) { + if (a->type() != b->type()) { + return a->type() > b->type(); + } else { + return c.compare(a->name(), b->name()) < 0; + } + }); +} + +void AppsModel::checkSycocaChanges(const QStringList &changes) +{ + if (changes.contains(QLatin1String("services")) || changes.contains(QLatin1String("apps")) || changes.contains(QLatin1String("xdgdata-apps"))) { + m_changeTimer->start(); + } +} + +void AppsModel::entryChanged(AbstractEntry *entry) +{ + int i = m_entryList.indexOf(entry); + + if (i != -1) { + QModelIndex idx = index(i, 0); + emit dataChanged(idx, idx); + } +} + +void AppsModel::classBegin() +{ + +} + +void AppsModel::componentComplete() +{ + m_complete = true; + + if (m_autoPopulate) { + refresh(); + } +} diff --git a/applets/kicker/plugin/computermodel.h b/applets/kicker/plugin/computermodel.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/computermodel.h @@ -0,0 +1,119 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef COMPUTERMODEL_H +#define COMPUTERMODEL_H + +#include "forwardingmodel.h" +#include "appentry.h" + +#include +#include + +class SimpleFavoritesModel; + +class KConcatenateRowsProxyModel; +class KFilePlacesModel; + +namespace Solid { + class Device; +} + +class FilteredPlacesModel : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + explicit FilteredPlacesModel(QObject *parent = nullptr); + ~FilteredPlacesModel() override; + + QUrl url(const QModelIndex &index) const; + bool isDevice(const QModelIndex &index) const; + Solid::Device deviceForIndex(const QModelIndex &index) const; + + protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + + private: + KFilePlacesModel *m_placesModel; +}; + +class RunCommandModel : public AbstractModel +{ + Q_OBJECT + + public: + RunCommandModel(QObject *parent = nullptr); + ~RunCommandModel() override; + + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; +}; + +class ComputerModel : public ForwardingModel +{ + Q_OBJECT + + Q_PROPERTY(int appNameFormat READ appNameFormat WRITE setAppNameFormat NOTIFY appNameFormatChanged) + Q_PROPERTY(QObject* appletInterface READ appletInterface WRITE setAppletInterface NOTIFY appletInterfaceChanged) + Q_PROPERTY(QStringList systemApplications READ systemApplications WRITE setSystemApplications NOTIFY systemApplicationsChanged) + + public: + explicit ComputerModel(QObject *parent = nullptr); + ~ComputerModel() override; + + QString description() const override; + + int appNameFormat() const; + void setAppNameFormat(int format); + + QObject *appletInterface() const; + void setAppletInterface(QObject *appletInterface); + + QStringList systemApplications() const; + void setSystemApplications(const QStringList &apps); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + Q_SIGNALS: + void appNameFormatChanged() const; + void appletInterfaceChanged() const; + void systemApplicationsChanged() const; + + private Q_SLOTS: + void onSetupDone(Solid::ErrorType error, QVariant errorData, const QString &udi); + + private: + KConcatenateRowsProxyModel *m_concatProxy; + RunCommandModel *m_runCommandModel; + SimpleFavoritesModel *m_systemAppsModel; + FilteredPlacesModel *m_filteredPlacesModel; + AppEntry::NameFormat m_appNameFormat; + QObject *m_appletInterface; +}; + +#endif diff --git a/applets/kicker/plugin/computermodel.cpp b/applets/kicker/plugin/computermodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/computermodel.cpp @@ -0,0 +1,298 @@ +/*************************************************************************** + * Copyright (C) 2007 Kevin Ottens * + * Copyright (C) 2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "computermodel.h" +#include "actionlist.h" +#include "simplefavoritesmodel.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "krunner_interface.h" + +FilteredPlacesModel::FilteredPlacesModel(QObject *parent) : QSortFilterProxyModel(parent) +, m_placesModel(new KFilePlacesModel(this)) +{ + setSourceModel(m_placesModel); + sort(0); +} + +FilteredPlacesModel::~FilteredPlacesModel() +{ +} + +QUrl FilteredPlacesModel::url(const QModelIndex &index) const +{ + return KFilePlacesModel::convertedUrl(m_placesModel->url(mapToSource(index))); +} + +bool FilteredPlacesModel::isDevice(const QModelIndex &index) const +{ + return m_placesModel->isDevice(mapToSource(index)); +} + +Solid::Device FilteredPlacesModel::deviceForIndex(const QModelIndex &index) const +{ + return m_placesModel->deviceForIndex(mapToSource(index)); +} + +bool FilteredPlacesModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + const QModelIndex index = m_placesModel->index(sourceRow, 0, sourceParent); + + return !m_placesModel->isHidden(index) + && !m_placesModel->data(index, KFilePlacesModel::FixedDeviceRole).toBool(); +} + +bool FilteredPlacesModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + bool lDevice = m_placesModel->isDevice(left); + bool rDevice = m_placesModel->isDevice(right); + + if (lDevice && !rDevice) { + return false; + } else if (!lDevice && rDevice) { + return true; + } + + return (left.row() < right.row()); +} + +RunCommandModel::RunCommandModel(QObject *parent) : AbstractModel(parent) +{ +} + +RunCommandModel::~RunCommandModel() +{ +} + +QString RunCommandModel::description() const +{ + return QString(); +} + +QVariant RunCommandModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return i18n("Show KRunner"); + } else if (role == Qt::DecorationRole) { + return QIcon::fromTheme(QStringLiteral("plasma-search")); + } else if (role == Kicker::DescriptionRole) { + return i18n("Search, calculate, or run a command"); + } else if (role == Kicker::GroupRole) { + return i18n("Applications"); + } + + return QVariant(); +} + +int RunCommandModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : (KAuthorized::authorize(QStringLiteral("run_command")) ? 1 : 0); +} + +Q_INVOKABLE bool RunCommandModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + Q_UNUSED(actionId) + Q_UNUSED(argument) + + if (row == 0 && KAuthorized::authorize(QStringLiteral("run_command"))) { + org::kde::krunner::App krunner(QStringLiteral("org.kde.krunner"), + QStringLiteral("/App"), QDBusConnection::sessionBus()); + krunner.display(); + + return true; + } + + return false; +} + +ComputerModel::ComputerModel(QObject *parent) : ForwardingModel(parent) +, m_concatProxy(new KConcatenateRowsProxyModel(this)) +, m_runCommandModel(new RunCommandModel(this)) +, m_systemAppsModel(new SimpleFavoritesModel(this)) +, m_filteredPlacesModel(new FilteredPlacesModel(this)) +, m_appNameFormat(AppEntry::NameOnly) +, m_appletInterface(nullptr) +{ + connect(m_systemAppsModel, &SimpleFavoritesModel::favoritesChanged, this, &ComputerModel::systemApplicationsChanged); + m_systemAppsModel->setFavorites(QStringList() << QStringLiteral("systemsettings.desktop")); + + m_concatProxy->addSourceModel(m_runCommandModel); + m_concatProxy->addSourceModel(m_systemAppsModel); + m_concatProxy->addSourceModel(m_filteredPlacesModel); + + setSourceModel(m_concatProxy); +} + +ComputerModel::~ComputerModel() +{ +} + +QString ComputerModel::description() const +{ + return i18n("Computer"); +} + +int ComputerModel::appNameFormat() const +{ + return m_appNameFormat; +} + +void ComputerModel::setAppNameFormat(int format) +{ + if (m_appNameFormat != (AppEntry::NameFormat)format) { + m_appNameFormat = (AppEntry::NameFormat)format; + + m_systemAppsModel->refresh(); + + emit appNameFormatChanged(); + } +} + +QObject *ComputerModel::appletInterface() const +{ + return m_appletInterface; +} + +void ComputerModel::setAppletInterface(QObject *appletInterface) +{ + if (m_appletInterface != appletInterface) { + m_appletInterface = appletInterface; + + emit appletInterfaceChanged(); + } +} + +QStringList ComputerModel::systemApplications() const +{ + return m_systemAppsModel->favorites(); +} + +void ComputerModel::setSystemApplications(const QStringList &apps) +{ + m_systemAppsModel->setFavorites(apps); +} + +QVariant ComputerModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + const QModelIndex sourceIndex = m_concatProxy->mapToSource(m_concatProxy->index(index.row(), + index.column())); + + bool isPlace = (sourceIndex.model() == m_filteredPlacesModel); + + if (isPlace) { + if (role == Kicker::DescriptionRole) { + if (m_filteredPlacesModel->isDevice(sourceIndex)) { + Solid::Device device = m_filteredPlacesModel->deviceForIndex(sourceIndex); + Solid::StorageAccess *access = device.as(); + + if (access) { + return access->filePath(); + } else { + return QString(); + } + } + } else if (role == Kicker::FavoriteIdRole) { + if (!m_filteredPlacesModel->isDevice(sourceIndex)) { + return m_filteredPlacesModel->url(sourceIndex); + } + } else if (role == Kicker::UrlRole) { + return m_filteredPlacesModel->url(sourceIndex); + } else if (role == Kicker::GroupRole) { + return sourceIndex.data(KFilePlacesModel::GroupRole).toString(); + } else if (role == Qt::DisplayRole || role == Qt::DecorationRole) { + return sourceIndex.data(role); + } + } else if (role == Kicker::GroupRole) { + return i18n("Applications"); + } else { + return sourceIndex.data(role); + } + + return QVariant(); +} + +bool ComputerModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + const QModelIndex sourceIndex = m_concatProxy->mapToSource(m_concatProxy->index(row, 0)); + + if (sourceIndex.model() == m_filteredPlacesModel) { + const QUrl &url = m_filteredPlacesModel->url(sourceIndex); + + if (url.isValid()) { + new KRun(url, nullptr); + + return true; + } + + Solid::Device device = m_filteredPlacesModel->deviceForIndex(sourceIndex); + Solid::StorageAccess *access = device.as(); + + if (access && !access->isAccessible()) { + connect(access, &Solid::StorageAccess::setupDone, this, &ComputerModel::onSetupDone); + access->setup(); + + return true; + } + } else { + AbstractModel *model = nullptr; + + if (sourceIndex.model() == m_systemAppsModel) { + model = m_systemAppsModel; + } else { + model = m_runCommandModel; + } + + return model->trigger(sourceIndex.row(), actionId, argument); + } + + return false; +} + +void ComputerModel::onSetupDone(Solid::ErrorType error, QVariant errorData, const QString &udi) +{ + Q_UNUSED(errorData); + + if (error != Solid::NoError) { + return; + } + + Solid::Device device(udi); + Solid::StorageAccess *access = device.as(); + + Q_ASSERT(access); + + new KRun(QUrl::fromLocalFile(access->filePath()), nullptr); +} diff --git a/dataengines/share/packagestructure/share_package.cpp b/applets/kicker/plugin/contactentry.h rename from dataengines/share/packagestructure/share_package.cpp rename to applets/kicker/plugin/contactentry.h --- a/dataengines/share/packagestructure/share_package.cpp +++ b/applets/kicker/plugin/contactentry.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 201 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,29 +17,39 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#include -#include +#ifndef CONTACTENTRY_H +#define CONTACTENTRY_H -#include "share_package.h" -#include "config-workspace.h" +#include "abstractentry.h" -SharePackage::SharePackage(QObject *, QVariantList) -{ +namespace KPeople { + class PersonData; } -void SharePackage::initPackage(KPackage::Package* package) +class ContactEntry : public AbstractEntry { -// KPackage::PackageStructure::initPackage(package); - package->addDirectoryDefinition("scripts", QStringLiteral("code"), i18n("Executable Scripts")); - QStringList mimetypes; - mimetypes << QStringLiteral("text/*"); - package->setMimeTypes( "scripts", mimetypes ); - - package->addFileDefinition("mainscript", QStringLiteral("code/main.js"), i18n("Main Script File")); - package->setDefaultPackageRoot(PLASMA_RELATIVE_DATA_INSTALL_DIR "shareprovider/"); -} + public: + explicit ContactEntry(AbstractModel *owner, const QString &id); + + EntryType type() const override { return RunnableType; } + + bool isValid() const override; + + QIcon icon() const override; + QString name() const override; + + QString id() const override; + QUrl url() const override; + + bool hasActions() const override; + QVariantList actions() const override; + + bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()) override; -K_EXPORT_KPACKAGE_PACKAGE_WITH_JSON(SharePackage, "plasma-packagestructure-share.json") + static void showPersonDetailsDialog(const QString &id); -#include "share_package.moc" + private: + KPeople::PersonData *m_personData; +}; +#endif diff --git a/applets/kicker/plugin/contactentry.cpp b/applets/kicker/plugin/contactentry.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/contactentry.cpp @@ -0,0 +1,143 @@ +/*************************************************************************** + * Copyright (C) 201 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "contactentry.h" +#include "actionlist.h" + +#include +#include + +#include +#include + +#include +#include +#include + +ContactEntry::ContactEntry(AbstractModel *owner, const QString &id) : AbstractEntry(owner) +, m_personData(nullptr) +{ + if (!id.isEmpty()) { + m_personData = new KPeople::PersonData(id); + + QObject::connect(m_personData, &KPeople::PersonData::dataChanged, + [this] { if (m_owner) { m_owner->entryChanged(this); } } + ); + } +} + +bool ContactEntry::isValid() const { + return m_personData; +} + +QIcon ContactEntry::icon() const +{ + if (m_personData) { + QPixmap photo = m_personData->photo(); + QBitmap mask(photo.size()); + QPainter painter(&mask); + mask.fill(Qt::white); + painter.setBrush(Qt::black); + painter.drawEllipse(0, 0, mask.width(), mask.height()); + photo.setMask(mask); + + photo = photo.scaled(m_owner->iconSize(), m_owner->iconSize(), + Qt::KeepAspectRatio, Qt::SmoothTransformation); + + KIconLoader::global()->drawOverlays(QStringList() << m_personData->presenceIconName(), photo, KIconLoader::Panel); + + return QIcon(photo); + } + + return QIcon::fromTheme(QStringLiteral("unknown")); +} + +QString ContactEntry::name() const +{ + if (m_personData) { + return m_personData->name(); + } + + return QString(); +} + +QString ContactEntry::id() const +{ + if (m_personData) { + const QString &id = m_personData->personUri(); + + if (id.isEmpty()) { + const QStringList uris = m_personData->contactUris(); + + if (!uris.isEmpty()) { + return uris.at(0); + } + } else { + return id; + } + } + + return QString(); +} + +QUrl ContactEntry::url() const +{ + if (m_personData) { + return QUrl(m_personData->personUri()); + } + + return QUrl(); +} + +bool ContactEntry::hasActions() const +{ + return m_personData; +} + +QVariantList ContactEntry::actions() const +{ + QVariantList actionList; + + actionList << Kicker::createActionItem(i18n("Show Contact Information..."), QString("identity"), QStringLiteral("showContactInfo")); + + return actionList; +} + +bool ContactEntry::run(const QString& actionId, const QVariant &argument) +{ + Q_UNUSED(argument) + + if (!m_personData) { + return false; + } + + if (actionId == QLatin1String("showContactInfo")) { + showPersonDetailsDialog(m_personData->personUri()); + } + + return false; +} + +void ContactEntry::showPersonDetailsDialog(const QString &id) { + KPeople::PersonDetailsDialog *view = new KPeople::PersonDetailsDialog(nullptr); + KPeople::PersonData *data = new KPeople::PersonData(id, view); + view->setPerson(data); + view->setAttribute(Qt::WA_DeleteOnClose); + view->show(); +} diff --git a/dataengines/share/shareservice.h b/applets/kicker/plugin/containmentinterface.h copy from dataengines/share/shareservice.h copy to applets/kicker/plugin/containmentinterface.h --- a/dataengines/share/shareservice.h +++ b/applets/kicker/plugin/containmentinterface.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2014 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,54 +17,42 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_SERVICE_H -#define SHARE_SERVICE_H +#ifndef CONTAINMENTINTERFACE_H +#define CONTAINMENTINTERFACE_H -#include "shareengine.h" - -#include -#include -#include - -class ShareProvider; +#include +#include namespace Plasma { - class ServiceJob; + class Containment; } -namespace KJSEmbed { - class Engine; -} - -class ShareService : public Plasma::Service +class ContainmentInterface : public QObject { Q_OBJECT -public: - explicit ShareService(ShareEngine *engine); - Plasma::ServiceJob *createJob(const QString &operation, - QMap ¶meters) override; -}; + public: + enum Target { + Desktop = 0, + Panel, + TaskManager + }; -class ShareJob : public Plasma::ServiceJob -{ - Q_OBJECT + Q_ENUM(Target) + + explicit ContainmentInterface(QObject *parent = nullptr); + ~ContainmentInterface() override; + + static Q_INVOKABLE bool mayAddLauncher(QObject *appletInterface, Target target, const QString &entryPath = QString()); -public: - ShareJob(const QString &destination, const QString &operation, - QMap ¶meters, QObject *parent = nullptr); - ~ShareJob() override; - void start() override; + static Q_INVOKABLE void addLauncher(QObject *appletInterface, Target target, const QString &entryPath); -public Q_SLOTS: - void publish(); - void showResult(const QString &url); - void showError(const QString &msg); + static Q_INVOKABLE QObject* screenContainment(QObject *appletInterface); + static Q_INVOKABLE bool screenContainmentMutable(QObject *appletInterface); + static Q_INVOKABLE void ensureMutable(Plasma::Containment *containment); -private: - QScopedPointer m_engine; - ShareProvider *m_provider; - KPackage::Package m_package; + private: + static QStringList m_knownTaskManagers; }; -#endif // SHARE_SERVICE +#endif diff --git a/applets/kicker/plugin/containmentinterface.cpp b/applets/kicker/plugin/containmentinterface.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/containmentinterface.cpp @@ -0,0 +1,252 @@ +/*************************************************************************** + * Copyright (C) 2014 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "containmentinterface.h" + +#include +#include +#include + +#include + +#include + +// FIXME HACK TODO: Unfortunately we have no choice but to hard-code a list of +// applets we know to expose the correct interface right now -- this is slated +// for replacement with some form of generic service. +QStringList ContainmentInterface::m_knownTaskManagers = QStringList() << QLatin1String("org.kde.plasma.taskmanager") + << QLatin1String("org.kde.plasma.icontasks") + << QLatin1String("org.kde.plasma.expandingiconstaskmanager"); + +ContainmentInterface::ContainmentInterface(QObject *parent) : QObject(parent) +{ +} + +ContainmentInterface::~ContainmentInterface() +{ +} + +bool ContainmentInterface::mayAddLauncher(QObject *appletInterface, ContainmentInterface::Target target, const QString &entryPath) +{ + if (!appletInterface) { + return false; + } + + Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); + Plasma::Containment *containment = applet->containment(); + + if (!containment) { + return false; + } + + Plasma::Corona *corona = containment->corona(); + + if (!corona) { + return false; + } + + switch (target) { + case Desktop: { + containment = corona->containmentForScreen(containment->screen()); + + if (containment) { + return (containment->immutability() == Plasma::Types::Mutable); + } + + break; + } + case Panel: { + if (containment->pluginMetaData().pluginId() == QLatin1String("org.kde.panel")) + { + return (containment->immutability() == Plasma::Types::Mutable); + } + + break; + } + case TaskManager: { + if (!entryPath.isEmpty() && containment->pluginMetaData().pluginId() == QLatin1String("org.kde.panel")) + { + const Plasma::Applet *taskManager = nullptr; + + foreach(const Plasma::Applet *applet, containment->applets()) { + if (m_knownTaskManagers.contains(applet->pluginMetaData().pluginId())) { + taskManager = applet; + + break; + } + } + + if (taskManager) { + QQuickItem* gObj = qobject_cast(taskManager->property("_plasma_graphicObject").value()); + + if (!gObj || !gObj->childItems().count()) { + return false; + } + + QQuickItem *rootItem = gObj->childItems().first(); + + QVariant ret; + + QMetaObject::invokeMethod(rootItem, "hasLauncher", Q_RETURN_ARG(QVariant, ret), + Q_ARG(QVariant, QUrl::fromLocalFile(entryPath))); + + return !ret.toBool(); + } + } + + break; + } + } + + return false; +} + +void ContainmentInterface::addLauncher(QObject *appletInterface, ContainmentInterface::Target target, const QString &entryPath) +{ + if (!appletInterface) { + return; + } + + Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); + Plasma::Containment *containment = applet->containment(); + + if (!containment) { + return; + } + + Plasma::Corona *corona = containment->corona(); + + if (!corona) { + return; + } + + switch (target) { + case Desktop: { + containment = corona->containmentForScreen(containment->screen()); + + if (!containment) { + return; + } + + const QStringList &containmentProvides = KPluginMetaData::readStringList(containment->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); + + if (containmentProvides.contains(QLatin1String("org.kde.plasma.filemanagement"))) { + QQuickItem* gObj = qobject_cast(containment->property("_plasma_graphicObject").value()); + + if (!gObj || !gObj->childItems().count()) { + return; + } + + QQuickItem *rootItem = nullptr; + + foreach(QQuickItem *item, gObj->childItems()) { + if (item->objectName() == QLatin1String("folder")) { + rootItem = item; + + break; + } + } + + if (rootItem) { + QMetaObject::invokeMethod(rootItem, "addLauncher", Q_ARG(QVariant, QUrl::fromLocalFile(entryPath))); + } + } else { + containment->createApplet(QStringLiteral("org.kde.plasma.icon"), QVariantList() << entryPath); + } + + break; + } + case Panel: { + if (containment->pluginMetaData().pluginId() == QLatin1String("org.kde.panel")) + { + containment->createApplet(QStringLiteral("org.kde.plasma.icon"), QVariantList() << entryPath); + } + + break; + } + case TaskManager: { + if (containment->pluginMetaData().pluginId() == QLatin1String("org.kde.panel")) + { + const Plasma::Applet *taskManager = nullptr; + + foreach(const Plasma::Applet *applet, containment->applets()) { + if (m_knownTaskManagers.contains(applet->pluginMetaData().pluginId())) { + taskManager = applet; + + break; + } + } + + if (taskManager) { + QQuickItem* gObj = qobject_cast(taskManager->property("_plasma_graphicObject").value()); + + if (!gObj || !gObj->childItems().count()) { + return; + } + + QQuickItem *rootItem = gObj->childItems().first(); + + QMetaObject::invokeMethod(rootItem, "addLauncher", Q_ARG(QVariant, QUrl::fromLocalFile(entryPath))); + } + } + + break; + } + } +} + +QObject* ContainmentInterface::screenContainment(QObject *appletInterface) +{ + if (!appletInterface) { + return nullptr; + } + + const Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); + Plasma::Containment *containment = applet->containment(); + + if (!containment) { + return nullptr; + } + + Plasma::Corona *corona = containment->corona(); + + if (!corona) { + return nullptr; + } + + return corona->containmentForScreen(containment->screen()); +} + +bool ContainmentInterface::screenContainmentMutable(QObject *appletInterface) +{ + const Plasma::Containment *containment = static_cast(screenContainment(appletInterface)); + + if (containment) { + return (containment->immutability() == Plasma::Types::Mutable); + } + + return false; +} + +void ContainmentInterface::ensureMutable(Plasma::Containment *containment) +{ + if (containment && containment->immutability() != Plasma::Types::Mutable) { + containment->actions()->action(QStringLiteral("lock widgets"))->trigger(); + } +} diff --git a/applets/kicker/plugin/dashboardwindow.h b/applets/kicker/plugin/dashboardwindow.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/dashboardwindow.h @@ -0,0 +1,81 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef DASHBOARDWINDOW_H +#define DASHBOARDWINDOW_H + +#include + +#include +#include + +class DashboardWindow : public QQuickWindow +{ + Q_OBJECT + + Q_PROPERTY(QQuickItem* mainItem READ mainItem WRITE setMainItem NOTIFY mainItemChanged) + Q_PROPERTY(QQuickItem* visualParent READ visualParent WRITE setVisualParent NOTIFY visualParentChanged) + Q_PROPERTY(QQuickItem* keyEventProxy READ keyEventProxy WRITE setKeyEventProxy NOTIFY keyEventProxyChanged) + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged) + + Q_CLASSINFO("DefaultProperty", "mainItem") + + public: + explicit DashboardWindow(QQuickItem *parent = nullptr); + ~DashboardWindow() override; + + QQuickItem *mainItem() const; + void setMainItem(QQuickItem *item); + + QQuickItem *visualParent() const; + void setVisualParent(QQuickItem *item); + + QQuickItem *keyEventProxy() const; + void setKeyEventProxy(QQuickItem *item); + + QColor backgroundColor() const; + void setBackgroundColor(const QColor &color); + + Q_INVOKABLE void toggle(); + + Q_SIGNALS: + void mainItemChanged() const; + void visualParentChanged() const; + void keyEventProxyChanged() const; + void backgroundColorChanged() const; + void keyEscapePressed() const; + + private Q_SLOTS: + void updateTheme(); + void visualParentWindowChanged(QQuickWindow *window); + void visualParentScreenChanged(QScreen *screen); + + protected: + bool event(QEvent *event) override; + void keyPressEvent(QKeyEvent *e) override; + + private: + QQuickItem *m_mainItem; + QPointer m_visualParentItem; + QPointer m_visualParentWindow; + QPointer m_keyEventProxy; + Plasma::Theme m_theme; +}; + +#endif diff --git a/applets/kicker/plugin/dashboardwindow.cpp b/applets/kicker/plugin/dashboardwindow.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/dashboardwindow.cpp @@ -0,0 +1,235 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "dashboardwindow.h" + +#include +#include +#include + +#include +#include + +DashboardWindow::DashboardWindow(QQuickItem *parent) : QQuickWindow(parent ? parent->window() : nullptr) +, m_mainItem(nullptr) +, m_visualParentItem(nullptr) +, m_visualParentWindow(nullptr) +{ + setClearBeforeRendering(true); + setFlags(Qt::FramelessWindowHint); + + setIcon(QIcon::fromTheme(QStringLiteral("plasma"))); + + connect(&m_theme, &Plasma::Theme::themeChanged, this, &DashboardWindow::updateTheme); +} + +DashboardWindow::~DashboardWindow() +{ +} + +QQuickItem *DashboardWindow::mainItem() const +{ + return m_mainItem; +} + +void DashboardWindow::setMainItem(QQuickItem *item) +{ + if (m_mainItem != item) { + if (m_mainItem) { + m_mainItem->setVisible(false); + } + + m_mainItem = item; + + if (m_mainItem) { + m_mainItem->setVisible(isVisible()); + m_mainItem->setParentItem(contentItem()); + } + + emit mainItemChanged(); + } +} + +QQuickItem *DashboardWindow::visualParent() const +{ + return m_visualParentItem; +} + +void DashboardWindow::setVisualParent(QQuickItem *item) +{ + if (m_visualParentItem != item) { + if (m_visualParentItem) { + disconnect(m_visualParentItem.data(), &QQuickItem::windowChanged, this, &DashboardWindow::visualParentWindowChanged); + } + + m_visualParentItem = item; + + if (m_visualParentItem) { + if (m_visualParentItem->window()) { + visualParentWindowChanged(m_visualParentItem->window()); + } + + connect(m_visualParentItem.data(), &QQuickItem::windowChanged, this, &DashboardWindow::visualParentWindowChanged); + } + + emit visualParentChanged(); + } +} + +QColor DashboardWindow::backgroundColor() const +{ + return color(); +} + +void DashboardWindow::setBackgroundColor(const QColor &c) +{ + if (color() != c) { + setColor(c); + + emit backgroundColorChanged(); + } +} + +QQuickItem *DashboardWindow::keyEventProxy() const +{ + return m_keyEventProxy; +} + +void DashboardWindow::setKeyEventProxy(QQuickItem *item) +{ + if (m_keyEventProxy != item) { + m_keyEventProxy = item; + + emit keyEventProxyChanged(); + } +} + +void DashboardWindow::toggle() { + if (isVisible()) { + close(); + } else { + resize(screen()->size()); + showFullScreen(); + KWindowSystem::forceActiveWindow(winId()); + } +} + +bool DashboardWindow::event(QEvent *event) +{ + if (event->type() == QEvent::Expose) { + // FIXME TODO: We can remove this once we depend on Qt 5.6.1+. + // See: https://bugreports.qt.io/browse/QTBUG-26978 + KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); + } else if (event->type() == QEvent::PlatformSurface) { + const QPlatformSurfaceEvent *pSEvent = static_cast(event); + + if (pSEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { + KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); + } + } else if (event->type() == QEvent::Show) { + updateTheme(); + + if (m_mainItem) { + m_mainItem->setVisible(true); + } + } else if (event->type() == QEvent::Hide) { + if (m_mainItem) { + m_mainItem->setVisible(false); + } + } else if (event->type() == QEvent::FocusOut) { + if (isVisible()) { + KWindowSystem::raiseWindow(winId()); + KWindowSystem::forceActiveWindow(winId()); + } + } + + return QQuickWindow::event(event); +} + +void DashboardWindow::keyPressEvent(QKeyEvent *e) +{ + if (e->key() == Qt::Key_Escape) { + emit keyEscapePressed(); + + return; + } else if (m_keyEventProxy && !m_keyEventProxy->hasActiveFocus() + && !(e->key() == Qt::Key_Home) + && !(e->key() == Qt::Key_End) + && !(e->key() == Qt::Key_Left) + && !(e->key() == Qt::Key_Up) + && !(e->key() == Qt::Key_Right) + && !(e->key() == Qt::Key_Down) + && !(e->key() == Qt::Key_PageUp) + && !(e->key() == Qt::Key_PageDown) + && !(e->key() == Qt::Key_Enter) + && !(e->key() == Qt::Key_Return) + && !(e->key() == Qt::Key_Menu) + && !(e->key() == Qt::Key_Tab) + && !(e->key() == Qt::Key_Backtab)) { + + QPointer previousFocusItem = activeFocusItem(); + + m_keyEventProxy->forceActiveFocus(); + QEvent* eventCopy = new QKeyEvent(e->type(), e->key(), e->modifiers(), + e->nativeScanCode(), e->nativeVirtualKey(), e->nativeModifiers(), + e->text(), e->isAutoRepeat(), e->count()); + QCoreApplication::postEvent(this, eventCopy); + + // We _need_ to do it twice to make sure the event ping-pong needed + // for delivery happens before we sap focus again. + QCoreApplication::processEvents(); + QCoreApplication::processEvents(); + + if (previousFocusItem) { + previousFocusItem->forceActiveFocus(); + } + + return; + } + + QQuickWindow::keyPressEvent(e); +} + +void DashboardWindow::updateTheme() +{ + KWindowEffects::enableBlurBehind(winId(), true); +} + +void DashboardWindow::visualParentWindowChanged(QQuickWindow *window) +{ + if (m_visualParentWindow) { + disconnect(m_visualParentWindow.data(), &QQuickWindow::screenChanged, this, &DashboardWindow::visualParentScreenChanged); + } + + m_visualParentWindow = window; + + if (m_visualParentWindow) { + visualParentScreenChanged(m_visualParentWindow->screen()); + + connect(m_visualParentWindow.data(), &QQuickWindow::screenChanged, this, &DashboardWindow::visualParentScreenChanged); + } +} + +void DashboardWindow::visualParentScreenChanged(QScreen *screen) +{ + if (screen) { + setScreen(screen); + setGeometry(screen->geometry()); + } +} diff --git a/dataengines/share/shareservice.h b/applets/kicker/plugin/draghelper.h copy from dataengines/share/shareservice.h copy to applets/kicker/plugin/draghelper.h --- a/dataengines/share/shareservice.h +++ b/applets/kicker/plugin/draghelper.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2013 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,54 +17,44 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_SERVICE_H -#define SHARE_SERVICE_H +#ifndef DRAGHELPER_H +#define DRAGHELPER_H -#include "shareengine.h" +#include +#include +#include -#include -#include -#include +class QQuickItem; -class ShareProvider; - -namespace Plasma { - class ServiceJob; -} - -namespace KJSEmbed { - class Engine; -} - -class ShareService : public Plasma::Service +class DragHelper : public QObject { Q_OBJECT - -public: - explicit ShareService(ShareEngine *engine); - Plasma::ServiceJob *createJob(const QString &operation, - QMap ¶meters) override; -}; - -class ShareJob : public Plasma::ServiceJob -{ - Q_OBJECT - -public: - ShareJob(const QString &destination, const QString &operation, - QMap ¶meters, QObject *parent = nullptr); - ~ShareJob() override; - void start() override; - -public Q_SLOTS: - void publish(); - void showResult(const QString &url); - void showError(const QString &msg); - -private: - QScopedPointer m_engine; - ShareProvider *m_provider; - KPackage::Package m_package; + Q_PROPERTY(int dragIconSize READ dragIconSize WRITE setDragIconSize NOTIFY dragIconSizeChanged) + Q_PROPERTY(bool dragging READ isDragging NOTIFY draggingChanged) + + public: + explicit DragHelper(QObject *parent = nullptr); + ~DragHelper() override; + + int dragIconSize() const; + void setDragIconSize(int size); + bool isDragging() const { return m_dragging; } + + Q_INVOKABLE bool isDrag(int oldX, int oldY, int newX, int newY) const; + Q_INVOKABLE void startDrag(QQuickItem* item, const QUrl &url = QUrl(), const QIcon &icon = QIcon(), + const QString &extraMimeType = QString(), const QString &extraMimeData = QString()); + + Q_SIGNALS: + void dragIconSizeChanged() const; + void dropped() const; + void draggingChanged() const; + + private: + int m_dragIconSize; + bool m_dragging; + Q_INVOKABLE void doDrag(QQuickItem* item, const QUrl &url = QUrl(), const QIcon &icon = QIcon(), + const QString &extraMimeType = QString(), const QString &extraMimeData = QString()); + void setDragging(bool dragging); }; -#endif // SHARE_SERVICE +#endif diff --git a/applets/kicker/plugin/draghelper.cpp b/applets/kicker/plugin/draghelper.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/draghelper.cpp @@ -0,0 +1,113 @@ +/*************************************************************************** + * Copyright (C) 2013 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "draghelper.h" + +#include +#include +#include +#include +#include +#include + +DragHelper::DragHelper(QObject* parent) : QObject(parent) +, m_dragIconSize(32) +, m_dragging(false) +{ +} + +DragHelper::~DragHelper() +{ +} + +int DragHelper::dragIconSize() const +{ + return m_dragIconSize; +} + +void DragHelper::setDragIconSize(int size) +{ + if (m_dragIconSize != size) { + m_dragIconSize = size; + + emit dragIconSizeChanged(); + } +} + +bool DragHelper::isDrag(int oldX, int oldY, int newX, int newY) const +{ + return ((QPoint(oldX, oldY) - QPoint(newX, newY)).manhattanLength() >= QApplication::startDragDistance()); +} + +void DragHelper::startDrag(QQuickItem *item, const QUrl &url, const QIcon &icon, + const QString &extraMimeType, const QString &extraMimeData) +{ + // This allows the caller to return, making sure we don't crash if + // the caller is destroyed mid-drag (as can happen due to a sycoca + // change). + + QMetaObject::invokeMethod(this, "doDrag", Qt::QueuedConnection, + Q_ARG(QQuickItem*, item), Q_ARG(QUrl, url), Q_ARG(QIcon, icon), + Q_ARG(QString, extraMimeType), Q_ARG(QString, extraMimeData)); +} + +void DragHelper::doDrag(QQuickItem *item, const QUrl &url, const QIcon &icon, + const QString &extraMimeType, const QString &extraMimeData) +{ + setDragging(true); + + if (item && item->window() && item->window()->mouseGrabberItem()) { + item->window()->mouseGrabberItem()->ungrabMouse(); + } + + QDrag *drag = new QDrag(item); + + QMimeData *mimeData = new QMimeData(); + + if (!url.isEmpty()) { + mimeData->setUrls(QList() << url); + } + + if (!extraMimeType.isEmpty() && !extraMimeData.isEmpty()) { + mimeData->setData(extraMimeType, extraMimeData.toLatin1()); + } + + drag->setMimeData(mimeData); + + if (!icon.isNull()) { + drag->setPixmap(icon.pixmap(m_dragIconSize, m_dragIconSize)); + } + + drag->exec(); + + emit dropped(); + + // Ensure dragging is still true when onRelease is called. + QTimer::singleShot(0, qApp, [this] { + setDragging(false); + }); +} + +void DragHelper::setDragging(bool dragging) +{ + if (m_dragging == dragging) + return; + m_dragging = dragging; + emit draggingChanged(); +} diff --git a/applets/lock_logout/contents/config/config.qml b/applets/kicker/plugin/fileentry.h copy from applets/lock_logout/contents/config/config.qml copy to applets/kicker/plugin/fileentry.h --- a/applets/lock_logout/contents/config/config.qml +++ b/applets/kicker/plugin/fileentry.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2013 by Sebastian Kügler * + * Copyright (C) 2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,14 +17,37 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -import QtQuick 2.0 +#ifndef FILEENTRY_H +#define FILEENTRY_H -import org.kde.plasma.configuration 2.0 +#include "abstractentry.h" -ConfigModel { - ConfigCategory { - name: i18n("General") - icon: "system-shutdown" - source: "ConfigGeneral.qml" - } -} +class KFileItem; + +class FileEntry : public AbstractEntry +{ + public: + explicit FileEntry(AbstractModel *owner, const QUrl &url); + ~FileEntry() override; + + EntryType type() const override { return RunnableType; } + + bool isValid() const override; + + QIcon icon() const override; + QString name() const override; + QString description() const override; + + QString id() const override; + QUrl url() const override; + + bool hasActions() const override; + QVariantList actions() const override; + + bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()) override; + + private: + KFileItem *m_fileItem; +}; + +#endif diff --git a/applets/kicker/plugin/fileentry.cpp b/applets/kicker/plugin/fileentry.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/fileentry.cpp @@ -0,0 +1,123 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "fileentry.h" +#include "actionlist.h" + +#include +#include + +FileEntry::FileEntry(AbstractModel *owner, const QUrl &url) : AbstractEntry(owner) +, m_fileItem(nullptr) +{ + if (url.isValid()) { + m_fileItem = new KFileItem(url); + m_fileItem->determineMimeType(); + } +} + +FileEntry::~FileEntry() +{ + delete m_fileItem; +} + +bool FileEntry::isValid() const +{ + return m_fileItem && (m_fileItem->isFile() || m_fileItem->isDir()); +} + +QIcon FileEntry::icon() const +{ + if (m_fileItem) { + return QIcon::fromTheme(m_fileItem->iconName(), QIcon::fromTheme(QStringLiteral("unknown"))); + } + + return QIcon::fromTheme(QStringLiteral("unknown")); +} + +QString FileEntry::name() const +{ + if (m_fileItem) { + return m_fileItem->text(); + } + + return QString(); +} + +QString FileEntry::description() const +{ + if (m_fileItem) { + return m_fileItem->url().toString(QUrl::PreferLocalFile); + } + + return QString(); +} + +QString FileEntry::id() const +{ + if (m_fileItem) { + return m_fileItem->url().toString(); + } + + return QString(); +} + +QUrl FileEntry::url() const +{ + if (m_fileItem) { + return m_fileItem->url(); + } + + return QUrl(); +} + +bool FileEntry::hasActions() const +{ + return m_fileItem && m_fileItem->isFile(); +} + +QVariantList FileEntry::actions() const +{ + if (m_fileItem) { + return Kicker::createActionListForFileItem(*m_fileItem); + } + + return QVariantList(); +} + +bool FileEntry::run(const QString& actionId, const QVariant &argument) +{ + if (!m_fileItem) { + return false; + } + + if (actionId.isEmpty()) { + new KRun(m_fileItem->url(), nullptr); + + return true; + } else { + bool close = false; + + if (Kicker::handleFileItemAction(*m_fileItem, actionId, argument, &close)) { + return close; + } + } + + return false; +} diff --git a/applets/kicker/plugin/forwardingmodel.h b/applets/kicker/plugin/forwardingmodel.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/forwardingmodel.h @@ -0,0 +1,77 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef FORWARDINGMODEL_H +#define FORWARDINGMODEL_H + +#include "abstractmodel.h" + +#include + +class ForwardingModel : public AbstractModel +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged) + + public: + explicit ForwardingModel(QObject *parent = nullptr); + ~ForwardingModel() override; + + QString description() const override; + + QAbstractItemModel *sourceModel() const; + virtual void setSourceModel(QAbstractItemModel *sourceModel); + + bool canFetchMore(const QModelIndex &parent) const override; + void fetchMore(const QModelIndex &parent) override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + Q_INVOKABLE QString labelForRow(int row) override; + + Q_INVOKABLE AbstractModel *modelForRow(int row) override; + + AbstractModel* favoritesModel() override; + + int separatorCount() const override; + + public Q_SLOTS: + void reset(); + + Q_SIGNALS: + void sourceModelChanged() const; + + protected: + QModelIndex indexToSourceIndex(const QModelIndex &index) const; + + void connectSignals(); + void disconnectSignals(); + + QPointer m_sourceModel; +}; + +#endif diff --git a/applets/kicker/plugin/forwardingmodel.cpp b/applets/kicker/plugin/forwardingmodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/forwardingmodel.cpp @@ -0,0 +1,265 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "forwardingmodel.h" + +ForwardingModel::ForwardingModel(QObject *parent) : AbstractModel(parent) +{ +} + +ForwardingModel::~ForwardingModel() +{ +} + +QString ForwardingModel::description() const +{ + if (!m_sourceModel) { + return QString(); + } + + AbstractModel *abstractModel = qobject_cast(m_sourceModel); + + if (!abstractModel) { + return QString(); + } + + return abstractModel->description(); +} + +QAbstractItemModel *ForwardingModel::sourceModel() const +{ + return m_sourceModel; +} + +void ForwardingModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + disconnectSignals(); + + beginResetModel(); + + m_sourceModel = sourceModel; + + connectSignals(); + + endResetModel(); + + emit countChanged(); + emit sourceModelChanged(); + emit descriptionChanged(); +} + +bool ForwardingModel::canFetchMore(const QModelIndex &parent) const +{ + if (!m_sourceModel) { + return false; + } + + return m_sourceModel->canFetchMore(indexToSourceIndex(parent)); +} + +void ForwardingModel::fetchMore(const QModelIndex &parent) +{ + if (m_sourceModel) { + m_sourceModel->fetchMore(indexToSourceIndex(parent)); + } +} + +QModelIndex ForwardingModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + if (!m_sourceModel) { + return QModelIndex(); + } + + return createIndex(row, column); +} + +QModelIndex ForwardingModel::parent(const QModelIndex &index) const +{ + Q_UNUSED(index) + + return QModelIndex(); +} + +QVariant ForwardingModel::data(const QModelIndex &index, int role) const +{ + if (!m_sourceModel) { + return QVariant(); + } + + return m_sourceModel->data(indexToSourceIndex(index), role); +} + +int ForwardingModel::rowCount(const QModelIndex &parent) const +{ + if (!m_sourceModel) { + return 0; + } + + return m_sourceModel->rowCount(indexToSourceIndex(parent)); +} + +QModelIndex ForwardingModel::indexToSourceIndex(const QModelIndex& index) const +{ + if (!m_sourceModel || !index.isValid()) { + return QModelIndex(); + } + + return m_sourceModel->index(index.row(), index.column(), + index.parent().isValid() ? indexToSourceIndex(index.parent()) : QModelIndex()); +} + +bool ForwardingModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (!m_sourceModel) { + return false; + } + + AbstractModel *abstractModel = qobject_cast(m_sourceModel); + + if (!abstractModel) { + return false; + } + + return abstractModel->trigger(row, actionId, argument); +} + +QString ForwardingModel::labelForRow(int row) +{ + if (!m_sourceModel) { + return QString(); + } + + AbstractModel *abstractModel = qobject_cast(m_sourceModel); + + if (!abstractModel) { + return QString(); + } + + return abstractModel->labelForRow(row); +} + +AbstractModel* ForwardingModel::modelForRow(int row) +{ + if (!m_sourceModel) { + return nullptr; + } + + AbstractModel *abstractModel = qobject_cast(m_sourceModel); + + if (!abstractModel) { + return nullptr; + } + + return abstractModel->modelForRow(row); +} + +AbstractModel* ForwardingModel::favoritesModel() +{ + AbstractModel *sourceModel = qobject_cast(m_sourceModel); + + if (sourceModel) { + return sourceModel->favoritesModel(); + } + + return AbstractModel::favoritesModel(); +} + +int ForwardingModel::separatorCount() const +{ + if (!m_sourceModel) { + return 0; + } + + AbstractModel *abstractModel = qobject_cast(m_sourceModel); + + if (!abstractModel) { + return 0; + } + + return abstractModel->separatorCount(); +} + +void ForwardingModel::reset() +{ + beginResetModel(); + endResetModel(); + + emit countChanged(); + emit separatorCountChanged(); +} + +void ForwardingModel::connectSignals() +{ + if (!m_sourceModel) { + return; + } + + connect(m_sourceModel, SIGNAL(destroyed()), this, SLOT(reset())); + connect(m_sourceModel.data(), &QAbstractItemModel::dataChanged, + this, &QAbstractItemModel::dataChanged, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::rowsAboutToBeInserted, + this, &QAbstractItemModel::rowsAboutToBeInserted, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::rowsAboutToBeMoved, + this, &QAbstractItemModel::rowsAboutToBeMoved, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::rowsAboutToBeRemoved, + this, &QAbstractItemModel::rowsAboutToBeRemoved, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::layoutAboutToBeChanged, + this, &QAbstractItemModel::layoutAboutToBeChanged, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::rowsInserted, + this, &QAbstractItemModel::rowsInserted, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::rowsInserted, + this, &AbstractModel::countChanged, Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::rowsMoved, + this, &QAbstractItemModel::rowsMoved, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::rowsRemoved, + this, &QAbstractItemModel::rowsRemoved, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::rowsRemoved, + this, &AbstractModel::countChanged, Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::modelAboutToBeReset, + this, &QAbstractItemModel::modelAboutToBeReset, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::modelReset, + this, &QAbstractItemModel::modelReset, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::modelReset, + this, &AbstractModel::countChanged, + Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::layoutChanged, + this, &QAbstractItemModel::layoutChanged, + Qt::UniqueConnection); +} + +void ForwardingModel::disconnectSignals() +{ + if (!m_sourceModel) { + return; + } + + disconnect(m_sourceModel, nullptr, this, nullptr); +} diff --git a/dataengines/share/packagestructure/share_package.h b/applets/kicker/plugin/funnelmodel.h rename from dataengines/share/packagestructure/share_package.h rename to applets/kicker/plugin/funnelmodel.h --- a/dataengines/share/packagestructure/share_package.h +++ b/applets/kicker/plugin/funnelmodel.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2014 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,16 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_PACKAGE_H -#define SHARE_PACKAGE_H +#ifndef FUNNELMODEL_H +#define FUNNELMODEL_H -#include +#include "forwardingmodel.h" -class SharePackage : public KPackage::PackageStructure +class FunnelModel : public ForwardingModel { -public: - explicit SharePackage(QObject *parent = nullptr, QVariantList args = QVariantList()); - void initPackage(KPackage::Package *package) override; + Q_OBJECT + + public: + explicit FunnelModel(QObject *parent = nullptr); + ~FunnelModel() override; + + void setSourceModel(QAbstractItemModel *model) override; }; #endif diff --git a/applets/kicker/plugin/funnelmodel.cpp b/applets/kicker/plugin/funnelmodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/funnelmodel.cpp @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2014 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "funnelmodel.h" + +FunnelModel::FunnelModel(QObject *parent) : ForwardingModel(parent) +{ +} + +FunnelModel::~FunnelModel() +{ +} + +void FunnelModel::setSourceModel(QAbstractItemModel *model) +{ + if (model && m_sourceModel == model) { + return; + } + + if (!model) { + reset(); + + return; + } + + connect(model, SIGNAL(destroyed(QObject*)), this, SLOT(reset())); + + if (!m_sourceModel) { + beginResetModel(); + + m_sourceModel = model; + + connectSignals(); + + endResetModel(); + + emit countChanged(); + + emit sourceModelChanged(); + emit descriptionChanged(); + + return; + } + + int oldCount = m_sourceModel->rowCount(); + int newCount = model->rowCount(); + + auto setNewModel = [this, model]() { + disconnectSignals(); + m_sourceModel = model; + connectSignals(); + }; + + if (newCount > oldCount) { + beginInsertRows(QModelIndex(), oldCount, newCount - 1); + setNewModel(); + endInsertRows(); + } else if (newCount < oldCount) { + if (newCount == 0) { + beginResetModel(); + setNewModel(); + endResetModel(); + } else { + beginRemoveRows(QModelIndex(), newCount, oldCount - 1); + setNewModel(); + endRemoveRows(); + } + } else { + setNewModel(); + } + + if (newCount > 0) { + emit dataChanged(index(0, 0), index(qMin(oldCount, newCount) - 1, 0)); + } + + if (oldCount != newCount) { + emit countChanged(); + } + + emit sourceModelChanged(); + emit descriptionChanged(); +} diff --git a/applets/kicker/plugin/kastatsfavoritesmodel.h b/applets/kicker/plugin/kastatsfavoritesmodel.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/kastatsfavoritesmodel.h @@ -0,0 +1,116 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * Copyright (C) 2016-2017 by Ivan Cukic * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef FAVORITESMODEL_H +#define FAVORITESMODEL_H + +#include "placeholdermodel.h" + +#include + +#include +#include + +class PlaceholderModel; + +namespace KActivities { + class Consumer; +namespace Stats { +namespace Terms { + class Activity; +} // namespace Terms +} // namespace Stats +} // namespace KActivities + +class KAStatsFavoritesModel : public PlaceholderModel +{ + Q_OBJECT + + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(QStringList favorites READ favorites WRITE setFavorites NOTIFY favoritesChanged) + Q_PROPERTY(int maxFavorites READ maxFavorites WRITE setMaxFavorites NOTIFY maxFavoritesChanged) + + Q_PROPERTY(QObject* activities READ activities CONSTANT) + + public: + explicit KAStatsFavoritesModel(QObject *parent = nullptr); + ~KAStatsFavoritesModel() override; + + QString description() const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + bool enabled() const; + void setEnabled(bool enable); + + QStringList favorites() const; + void setFavorites(const QStringList &favorites); + + int maxFavorites() const; + void setMaxFavorites(int max); + + Q_INVOKABLE bool isFavorite(const QString &id) const; + + Q_INVOKABLE void addFavorite(const QString &id, int index = -1); + Q_INVOKABLE void removeFavorite(const QString &id); + + Q_INVOKABLE void addFavoriteTo(const QString &id, const QString &activityId, int index = -1); + Q_INVOKABLE void removeFavoriteFrom(const QString &id, const QString &activityId); + + Q_INVOKABLE void setFavoriteOn(const QString &id, const QString &activityId); + + Q_INVOKABLE void portOldFavorites(const QStringList &ids); + + Q_INVOKABLE QStringList linkedActivitiesFor(const QString &id) const; + + Q_INVOKABLE void moveRow(int from, int to); + + Q_INVOKABLE void initForClient(const QString &client); + + QObject *activities() const; + Q_INVOKABLE QString activityNameForId(const QString &activityId) const; + + AbstractModel* favoritesModel() override; + + public Q_SLOTS: + void refresh() override; + + Q_SIGNALS: + void enabledChanged() const; + void favoritesChanged() const; + void maxFavoritesChanged() const; + + private: + class Private; + Private * d; + + AbstractEntry *favoriteFromId(const QString &id) const; + + void addFavoriteTo(const QString &id, const KActivities::Stats::Terms::Activity &activityId, int index = -1); + void removeFavoriteFrom(const QString &id, const KActivities::Stats::Terms::Activity &activityId); + + bool m_enabled; + + int m_maxFavorites; + + KActivities::Consumer *m_activities; +}; + +#endif diff --git a/applets/kicker/plugin/kastatsfavoritesmodel.cpp b/applets/kicker/plugin/kastatsfavoritesmodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/kastatsfavoritesmodel.cpp @@ -0,0 +1,719 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * Copyright (C) 2016-2017 by Ivan Cukic * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "kastatsfavoritesmodel.h" +#include "appentry.h" +#include "contactentry.h" +#include "fileentry.h" +#include "actionlist.h" +#include "debug.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +#define AGENT_APPLICATIONS QStringLiteral("org.kde.plasma.favorites.applications") +#define AGENT_CONTACTS QStringLiteral("org.kde.plasma.favorites.contacts") +#define AGENT_DOCUMENTS QStringLiteral("org.kde.plasma.favorites.documents") + +QString agentForUrl(const QString &url) +{ + return url.startsWith(QLatin1String("ktp:")) + ? AGENT_CONTACTS + : url.startsWith(QLatin1String("preferred:")) + ? AGENT_APPLICATIONS + : url.startsWith(QLatin1String("applications:")) + ? AGENT_APPLICATIONS + : (url.startsWith(QLatin1Char('/')) && !url.endsWith(QLatin1String(".desktop"))) + ? AGENT_DOCUMENTS + : (url.startsWith(QLatin1String("file:/")) && !url.endsWith(QLatin1String(".desktop"))) + ? AGENT_DOCUMENTS + // use applications as the default + : AGENT_APPLICATIONS; +} + +class KAStatsFavoritesModel::Private: public QAbstractListModel { +public: + class NormalizedId { + public: + NormalizedId() + { + } + + NormalizedId(const Private *parent, const QString &id) + { + if (id.isEmpty()) return; + + QSharedPointer entry = nullptr; + + if (parent->m_itemEntries.contains(id)) { + entry = parent->m_itemEntries[id]; + } else { + // This entry is not cached - it is temporary, + // so let's clean up when we exit this function + entry = parent->entryForResource(id); + } + + if (!entry || !entry->isValid()) { + qWarning() << "Entry is not valid" << id << entry; + m_id = id; + return; + } + + const auto url = entry->url(); + + qCDebug(KICKER_DEBUG) << "Original id is: " << id << ", and the url is" << url; + + // Preferred applications need special handling + if (entry->id().startsWith(QLatin1String("preferred:"))) { + m_id = entry->id(); + return; + } + + // If this is an application, use the applications:-format url + auto appEntry = dynamic_cast(entry.data()); + if (appEntry && !appEntry->menuId().isEmpty()) { + m_id = QLatin1String("applications:") + appEntry->menuId(); + return; + } + + // We want to resolve symbolic links not to have two paths + // refer to the same .desktop file + if (url.isLocalFile()) { + QFileInfo file(url.toLocalFile()); + + if (file.exists()) { + m_id = QUrl::fromLocalFile(file.canonicalFilePath()).toString(); + return; + } + } + + // If this is a file, we should have already covered it + if (url.scheme() == QLatin1String("file")) { + return; + } + + m_id = url.toString(); + } + + const QString& value() const + { + return m_id; + } + + bool operator==(const NormalizedId &other) const + { + return m_id == other.m_id; + } + + private: + QString m_id; + }; + + NormalizedId normalizedId(const QString &id) const + { + return NormalizedId(this, id); + } + + QSharedPointer entryForResource(const QString &resource) const + { + using SP = QSharedPointer; + + const auto agent = + agentForUrl(resource); + + if (agent == AGENT_CONTACTS) { + return SP(new ContactEntry(q, resource)); + + } else if (agent == AGENT_DOCUMENTS) { + if (resource.startsWith(QLatin1String("/"))) { + return SP(new FileEntry(q, QUrl::fromLocalFile(resource))); + } else { + return SP(new FileEntry(q, QUrl(resource))); + } + + } else if (agent == AGENT_APPLICATIONS) { + if (resource.startsWith(QLatin1String("applications:"))) { + return SP(new AppEntry(q, resource.mid(13))); + } else { + return SP(new AppEntry(q, resource)); + } + + } else { + return {}; + } + } + + Private(KAStatsFavoritesModel *parent, QString clientId) + : q(parent) + , m_query( + LinkedResources + | Agent { + AGENT_APPLICATIONS, + AGENT_CONTACTS, + AGENT_DOCUMENTS + } + | Type::any() + | Activity::current() + | Activity::global() + | Limit::all() + ) + , m_watcher(m_query) + , m_clientId(clientId) + { + // Connecting the watcher + connect(&m_watcher, &ResultWatcher::resultLinked, + [this] (const QString &resource) { + addResult(resource, -1); + }); + + connect(&m_watcher, &ResultWatcher::resultUnlinked, + [this] (const QString &resource) { + removeResult(resource); + }); + + // Loading the items order + const auto cfg = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerd-statsrc")); + + // We want first to check whether we have an ordering for this activity. + // If not, we will try to get a global one for this applet + + const QString thisGroupName = + QStringLiteral("Favorites-") + clientId + QStringLiteral("-") + m_activities.currentActivity(); + const QString globalGroupName = + QStringLiteral("Favorites-") + clientId + QStringLiteral("-global"); + + KConfigGroup thisCfgGroup(cfg, thisGroupName); + KConfigGroup globalCfgGroup(cfg, globalGroupName); + + QStringList ordering = + thisCfgGroup.readEntry("ordering", QStringList()) + + globalCfgGroup.readEntry("ordering", QStringList()); + + qCDebug(KICKER_DEBUG) << "Loading the ordering " << ordering; + + // Loading the results without emitting any model signals + qCDebug(KICKER_DEBUG) << "Query is" << m_query; + ResultSet results(m_query); + + for (const auto& result: results) { + qCDebug(KICKER_DEBUG) << "Got " << result.resource() << " -->"; + addResult(result.resource(), -1, false); + } + + // Normalizing all the ids + std::transform(ordering.begin(), ordering.end(), ordering.begin(), + [&] (const QString &item) { + return normalizedId(item).value(); + }); + + // Sorting the items in the cache + std::sort(m_items.begin(), m_items.end(), + [&] (const NormalizedId &left, const NormalizedId &right) { + auto leftIndex = ordering.indexOf(left.value()); + auto rightIndex = ordering.indexOf(right.value()); + + return (leftIndex == -1 && rightIndex == -1) ? + left.value() < right.value() : + + (leftIndex == -1) ? + false : + + (rightIndex == -1) ? + true : + + // otherwise + leftIndex < rightIndex; + }); + + // Debugging: + QVector itemStrings(m_items.size()); + std::transform(m_items.cbegin(), m_items.cend(), itemStrings.begin(), + [] (const NormalizedId &item) { + return item.value(); + }); + qCDebug(KICKER_DEBUG) << "After ordering: " << itemStrings; + } + + void addResult(const QString &_resource, int index, bool notifyModel = true) + { + // We want even files to have a proper URL + const auto resource = + _resource.startsWith(QLatin1Char('/')) ? QUrl::fromLocalFile(_resource).toString() : _resource; + + qCDebug(KICKER_DEBUG) << "Adding result" << resource << "already present?" << m_itemEntries.contains(resource); + + if (m_itemEntries.contains(resource)) return; + + auto entry = entryForResource(resource); + + if (!entry || !entry->isValid()) { + qCDebug(KICKER_DEBUG) << "Entry is not valid!"; + return; + } + + if (index == -1) { + index = m_items.count(); + } + + if (notifyModel) { + beginInsertRows(QModelIndex(), index, index); + } + + auto url = entry->url(); + + m_itemEntries[resource] + = m_itemEntries[entry->id()] + = m_itemEntries[url.toString()] + = m_itemEntries[url.toLocalFile()] + = entry; + + auto normalized = normalizedId(resource); + m_items.insert(index, normalized); + m_itemEntries[normalized.value()] = entry; + + if (notifyModel) { + endInsertRows(); + saveOrdering(); + } + } + + void removeResult(const QString &resource) + { + auto normalized = normalizedId(resource); + + // If we know this item will not really be removed, + // but only that activities it is on have changed, + // lets leave it + if (m_ignoredItems.contains(normalized.value())) { + m_ignoredItems.removeAll(normalized.value()); + return; + } + + qCDebug(KICKER_DEBUG) << "Removing result" << resource; + + auto index = m_items.indexOf(normalizedId(resource)); + + if (index == -1) return; + + beginRemoveRows(QModelIndex(), index, index); + auto entry = m_itemEntries[resource]; + m_items.removeAt(index); + + // Removing the entry from the cache + QMutableHashIterator> i(m_itemEntries); + while (i.hasNext()) { + i.next(); + if (i.value() == entry) { + i.remove(); + } + } + + endRemoveRows(); + } + + + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + if (parent.isValid()) return 0; + + return m_items.count(); + } + + QVariant data(const QModelIndex &item, + int role = Qt::DisplayRole) const override + { + if (item.parent().isValid()) return QVariant(); + + const auto index = item.row(); + + const auto entry = m_itemEntries[m_items[index].value()]; + + return entry == nullptr ? QVariant() + : role == Qt::DisplayRole ? entry->name() + : role == Qt::DecorationRole ? entry->icon() + : role == Kicker::DescriptionRole ? entry->description() + : role == Kicker::FavoriteIdRole ? entry->id() + : role == Kicker::UrlRole ? entry->url() + : role == Kicker::HasActionListRole ? entry->hasActions() + : role == Kicker::ActionListRole ? entry->actions() + : QVariant(); + } + + bool trigger(int row, const QString &actionId, const QVariant &argument) + { + if (row < 0 || row >= rowCount()) { + return false; + } + + const QString id = data(index(row, 0), Kicker::UrlRole).toString(); + + return m_itemEntries.contains(id) + ? m_itemEntries[id]->run(actionId, argument) + : false; + } + + void move(int from, int to) + { + if (from < 0) return; + if (from >= m_items.count()) return; + if (to < 0) return; + if (to >= m_items.count()) return; + + if (from == to) return; + + const int modelTo = to + (to > from ? 1 : 0); + + if (q->beginMoveRows(QModelIndex(), from, from, + QModelIndex(), modelTo)) { + m_items.move(from, to); + q->endMoveRows(); + + qCDebug(KICKER_DEBUG) << "Save ordering (from Private::move) -->"; + saveOrdering(); + } + } + + void saveOrdering() + { + QStringList ids; + + for (const auto& item: m_items) { + ids << item.value(); + } + + qCDebug(KICKER_DEBUG) << "Save ordering (from Private::saveOrdering) -->"; + saveOrdering(ids, m_clientId, m_activities.currentActivity()); + } + + static void saveOrdering(const QStringList &ids, const QString &clientId, const QString ¤tActivity) + { + const auto cfg = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerd-statsrc")); + + QStringList activities { + currentActivity, + QStringLiteral("global") + }; + + qCDebug(KICKER_DEBUG) << "Saving ordering for" << currentActivity << "and global" << ids; + + for (const auto& activity: activities) { + const QString groupName = + QStringLiteral("Favorites-") + clientId + QStringLiteral("-") + activity; + + KConfigGroup cfgGroup(cfg, groupName); + + cfgGroup.writeEntry("ordering", ids); + } + + cfg->sync(); + } + + KAStatsFavoritesModel *const q; + KActivities::Consumer m_activities; + Query m_query; + ResultWatcher m_watcher; + QString m_clientId; + + QVector m_items; + QHash> m_itemEntries; + QStringList m_ignoredItems; +}; + +KAStatsFavoritesModel::KAStatsFavoritesModel(QObject *parent) +: PlaceholderModel(parent) +, d(nullptr) // we have no client id yet +, m_enabled(true) +, m_maxFavorites(-1) +, m_activities(new KActivities::Consumer(this)) +{ + connect(m_activities, &KActivities::Consumer::currentActivityChanged, + this, [&] (const QString ¤tActivity) { + qCDebug(KICKER_DEBUG) << "Activity just got changed to" << currentActivity; + Q_UNUSED(currentActivity); + if (d) { + auto clientId = d->m_clientId; + initForClient(clientId); + } + }); +} + +KAStatsFavoritesModel::~KAStatsFavoritesModel() +{ + delete d; +} + +void KAStatsFavoritesModel::initForClient(const QString &clientId) +{ + qCDebug(KICKER_DEBUG) << "initForClient" << clientId; + + setSourceModel(nullptr); + delete d; + d = new Private( + this, + clientId + ); + + setSourceModel(d); +} + +QString KAStatsFavoritesModel::description() const +{ + return i18n("Favorites"); +} + +bool KAStatsFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + return d && d->trigger(row, actionId, argument); +} + +bool KAStatsFavoritesModel::enabled() const +{ + return m_enabled; +} + +int KAStatsFavoritesModel::maxFavorites() const +{ + return m_maxFavorites; +} + +void KAStatsFavoritesModel::setMaxFavorites(int max) +{ + Q_UNUSED(max); +} + +void KAStatsFavoritesModel::setEnabled(bool enable) +{ + if (m_enabled != enable) { + m_enabled = enable; + + emit enabledChanged(); + } +} + +QStringList KAStatsFavoritesModel::favorites() const +{ + qWarning() << "KAStatsFavoritesModel::favorites returns nothing, it is here just to keep the API backwards-compatible"; + return QStringList(); +} + +void KAStatsFavoritesModel::setFavorites(const QStringList& favorites) +{ + Q_UNUSED(favorites); + qWarning() << "KAStatsFavoritesModel::setFavorites is ignored"; +} + +bool KAStatsFavoritesModel::isFavorite(const QString &id) const +{ + return d && d->m_itemEntries.contains(id); +} + +void KAStatsFavoritesModel::portOldFavorites(const QStringList &ids) +{ + if (!d) return; + qCDebug(KICKER_DEBUG) << "portOldFavorites" << ids; + + const QString activityId = QStringLiteral(":global"); + std::for_each(ids.begin(), ids.end(), [&] (const QString &id) { + addFavoriteTo(id, activityId); + }); + + // Resetting the model + auto clientId = d->m_clientId; + setSourceModel(nullptr); + delete d; + d = nullptr; + + qCDebug(KICKER_DEBUG) << "Save ordering (from portOldFavorites) -->"; + Private::saveOrdering(ids, clientId, m_activities->currentActivity()); + + QTimer::singleShot(500, + std::bind(&KAStatsFavoritesModel::initForClient, this, clientId)); +} + +void KAStatsFavoritesModel::addFavorite(const QString &id, int index) +{ + qCDebug(KICKER_DEBUG) << "addFavorite" << id << index << " -->"; + addFavoriteTo(id, QStringLiteral(":global"), index); +} + +void KAStatsFavoritesModel::removeFavorite(const QString &id) +{ + qCDebug(KICKER_DEBUG) << "removeFavorite" << id << " -->"; + removeFavoriteFrom(id, QStringLiteral(":any")); +} + +void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const QString &activityId, int index) +{ + qCDebug(KICKER_DEBUG) << "addFavoriteTo" << id << activityId << index << " -->"; + addFavoriteTo(id, Activity(activityId), index); +} + +void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const QString &activityId) +{ + qCDebug(KICKER_DEBUG) << "removeFavoriteFrom" << id << activityId << " -->"; + removeFavoriteFrom(id, Activity(activityId)); +} + +void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const Activity &activity, int index) +{ + if (!d || id.isEmpty()) return; + + Q_ASSERT(!activity.values.isEmpty()); + + setDropPlaceholderIndex(-1); + + QStringList matchers { d->m_activities.currentActivity(), QStringLiteral(":global"), QStringLiteral(":current") }; + if (std::find_first_of(activity.values.cbegin(), activity.values.cend(), + matchers.cbegin(), matchers.cend()) != activity.values.cend()) { + d->addResult(id, index); + } + + const auto url = d->normalizedId(id).value(); + + qCDebug(KICKER_DEBUG) << "addFavoriteTo" << id << activity << index << url << " (actual)"; + + if (url.isEmpty()) return; + + d->m_watcher.linkToActivity(QUrl(url), activity, + Agent(agentForUrl(url))); +} + +void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const Activity &activity) +{ + if (!d || id.isEmpty()) return; + + const auto url = d->normalizedId(id).value(); + + Q_ASSERT(!activity.values.isEmpty()); + + qCDebug(KICKER_DEBUG) << "addFavoriteTo" << id << activity << url << " (actual)"; + + if (url.isEmpty()) return; + + d->m_watcher.unlinkFromActivity(QUrl(url), activity, + Agent(agentForUrl(url))); +} + +void KAStatsFavoritesModel::setFavoriteOn(const QString &id, const QString &activityId) +{ + if (!d || id.isEmpty()) return; + + const auto url = d->normalizedId(id).value(); + + qCDebug(KICKER_DEBUG) << "setFavoriteOn" << id << activityId << url << " (actual)"; + + qCDebug(KICKER_DEBUG) << "%%%%%%%%%%% Activity is" << activityId; + if (activityId.isEmpty() || activityId == QLatin1String(":any") || + activityId == QLatin1String(":global") || + activityId == m_activities->currentActivity()) { + d->m_ignoredItems << url; + } + + d->m_watcher.unlinkFromActivity(QUrl(url), Activity::any(), + Agent(agentForUrl(url))); + d->m_watcher.linkToActivity(QUrl(url), activityId, + Agent(agentForUrl(url))); +} + +void KAStatsFavoritesModel::moveRow(int from, int to) +{ + if (!d) return; + + d->move(from, to); +} + +AbstractModel *KAStatsFavoritesModel::favoritesModel() +{ + return this; +} + +void KAStatsFavoritesModel::refresh() +{ +} + +QObject *KAStatsFavoritesModel::activities() const +{ + return m_activities; +} + +QString KAStatsFavoritesModel::activityNameForId(const QString &activityId) const +{ + // It is safe to use a short-lived object here, + // we are always synced with KAMD in plasma + KActivities::Info info(activityId); + return info.name(); +} + +QStringList KAStatsFavoritesModel::linkedActivitiesFor(const QString &id) const +{ + if (!d) { + qCDebug(KICKER_DEBUG) << "Linked for" << id << "is empty, no Private instance"; + return {}; + } + + auto url = d->normalizedId(id).value(); + + if (url.startsWith(QLatin1String("file:"))) { + url = QUrl(url).toLocalFile(); + } + + if (url.isEmpty()) { + qCDebug(KICKER_DEBUG) << "The url for" << id << "is empty"; + return {}; + } + + auto query = LinkedResources + | Agent { + AGENT_APPLICATIONS, + AGENT_CONTACTS, + AGENT_DOCUMENTS + } + | Type::any() + | Activity::any() + | Url(url) + | Limit::all(); + + ResultSet results(query); + + for (const auto &result: results) { + qCDebug(KICKER_DEBUG) << "Returning" << result.linkedActivities() << "for" << id << url; + return result.linkedActivities(); + } + + qCDebug(KICKER_DEBUG) << "Returning empty list of activities for" << id << url; + return {}; +} + diff --git a/dataengines/share/backends/wstaw/contents/code/main.js b/applets/kicker/plugin/kickerplugin.h rename from dataengines/share/backends/wstaw/contents/code/main.js rename to applets/kicker/plugin/kickerplugin.h --- a/dataengines/share/backends/wstaw/contents/code/main.js +++ b/applets/kicker/plugin/kickerplugin.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010 by Artur Duque de Souza * + * Copyright (C) 2014 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,22 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -function url() { - return "http://wstaw.org"; -} +#ifndef KICKERPLUGIN_H +#define KICKERPLUGIN_H -function contentKey() { - return "pic"; -} +#include +#include -function setup() { -} +class KickerPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") -function handleResultData(data) { - var res = data.match("value=\"http://wstaw.org/m/.+\""); - if (res == null) { - provider.error(data); - return; - } - provider.success(res[0].replace("value=", "").replace("\"", "").replace("\"", "")); -} + public: + void registerTypes(const char *uri) override; +}; + +#endif // KICKERPLUGIN_H diff --git a/applets/kicker/plugin/kickerplugin.cpp b/applets/kicker/plugin/kickerplugin.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/kickerplugin.cpp @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (C) 2014 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "kickerplugin.h" +#include "abstractmodel.h" +#include "appsmodel.h" +#include "computermodel.h" +#include "containmentinterface.h" +#include "draghelper.h" +#include "simplefavoritesmodel.h" +#include "kastatsfavoritesmodel.h" +#include "dashboardwindow.h" +#include "funnelmodel.h" +#include "processrunner.h" +#include "recentusagemodel.h" +#include "rootmodel.h" +#include "runnermodel.h" +#include "submenu.h" +#include "systemmodel.h" +#include "systemsettings.h" +#include "wheelinterceptor.h" +#include "windowsystem.h" + +#include + +void KickerPlugin::registerTypes(const char *uri) +{ + Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.kicker")); + + qmlRegisterType(); + + qmlRegisterType(uri, 0, 1, "AppsModel"); + qmlRegisterType(uri, 0, 1, "ComputerModel"); + qmlRegisterType(uri, 0, 1, "ContainmentInterface"); + qmlRegisterType(uri, 0, 1, "DragHelper"); + qmlRegisterType(uri, 0, 1, "FavoritesModel"); + qmlRegisterType(uri, 0, 1, "KAStatsFavoritesModel"); + qmlRegisterType(uri, 0, 1, "DashboardWindow"); + qmlRegisterType(uri, 0, 1, "FunnelModel"); + qmlRegisterType(uri, 0, 1, "ProcessRunner"); + qmlRegisterType(uri, 0, 1, "RecentUsageModel"); + qmlRegisterType(uri, 0, 1, "RootModel"); + qmlRegisterType(uri, 0, 1, "RunnerModel"); + qmlRegisterType(uri, 0, 1, "SubMenu"); + qmlRegisterType(uri, 0, 1, "SystemModel"); + qmlRegisterType(uri, 0, 1, "SystemSettings"); + qmlRegisterType(uri, 0, 1, "WheelInterceptor"); + qmlRegisterType(uri, 0, 1, "WindowSystem"); +} diff --git a/dataengines/share/backends/simplestimagehosting/contents/code/main.js b/applets/kicker/plugin/menuentryeditor.h rename from dataengines/share/backends/simplestimagehosting/contents/code/main.js rename to applets/kicker/plugin/menuentryeditor.h --- a/dataengines/share/backends/simplestimagehosting/contents/code/main.js +++ b/applets/kicker/plugin/menuentryeditor.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010 by Artur Duque de Souza * + * Copyright (C) 2014 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,22 +17,23 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -function url() { - return "http://api.simplest-image-hosting.net/upload:image,default"; -} +#ifndef MENUENTRYEDITOR_H +#define MENUENTRYEDITOR_H -function contentKey() { - return "fileName"; -} +#include -function setup() { -} +class MenuEntryEditor : public QObject +{ + Q_OBJECT -function handleResultData(data) { - var res = data.match("800\n(http://.+)\n"); - if (res == "") { - provider.error(data); - return; - } - provider.success(data.replace("800", "").replace("\n", "")); -} + public: + explicit MenuEntryEditor(QObject *parent = nullptr); + ~MenuEntryEditor() override; + + bool canEdit(const QString &entryPath) const; + + public Q_SLOTS: + void edit(const QString &entryPath, const QString &menuId); +}; + +#endif diff --git a/applets/kicker/plugin/menuentryeditor.cpp b/applets/kicker/plugin/menuentryeditor.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/menuentryeditor.cpp @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2014 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "menuentryeditor.h" + +#include +#include +#include + +#include + +MenuEntryEditor::MenuEntryEditor(QObject* parent) : QObject(parent) +{ +} + +MenuEntryEditor::~MenuEntryEditor() +{ +} + +bool MenuEntryEditor::canEdit(const QString& entryPath) const +{ + KFileItemList itemList; + itemList << KFileItem(QUrl::fromLocalFile(entryPath)); + + return KPropertiesDialog::canDisplay(itemList); +} + +void MenuEntryEditor::edit(const QString& entryPath, const QString& menuId) +{ + const QString &appsPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); + const QUrl &entryUrl = QUrl::fromLocalFile(entryPath); + + if (!appsPath.isEmpty() && entryUrl.isValid()) { + const QDir appsDir(appsPath); + const QString &fileName = entryUrl.fileName(); + + if (appsDir.exists(fileName)) { + KPropertiesDialog::showDialog(entryUrl, nullptr, false); + } else { + if (!appsDir.exists()) { + if (!QDir::root().mkpath(appsPath)) { + return; + } + } + + KPropertiesDialog *dialog = new KPropertiesDialog(entryUrl, + QUrl::fromLocalFile(appsPath), menuId); + //KPropertiesDialog deletes itself + dialog->show(); + } + } +} diff --git a/applets/kicker/plugin/placeholdermodel.h b/applets/kicker/plugin/placeholdermodel.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/placeholdermodel.h @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * Copyright (C) 2017 by Ivan Cukic * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef PLACEHOLDERMODEL_H +#define PLACEHOLDERMODEL_H + +#include "abstractmodel.h" + +#include +#include + +class PlaceholderModel : public AbstractModel +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged) + Q_PROPERTY(int dropPlaceholderIndex READ dropPlaceholderIndex WRITE setDropPlaceholderIndex NOTIFY dropPlaceholderIndexChanged) + + public: + explicit PlaceholderModel(QObject *parent = nullptr); + ~PlaceholderModel() override; + + QString description() const override; + + QAbstractItemModel *sourceModel() const; + virtual void setSourceModel(QAbstractItemModel *sourceModel); + + bool canFetchMore(const QModelIndex &parent) const override; + void fetchMore(const QModelIndex &parent) override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + Q_INVOKABLE QString labelForRow(int row) override; + + Q_INVOKABLE AbstractModel *modelForRow(int row) override; + + AbstractModel* favoritesModel() override; + + int separatorCount() const override; + + int dropPlaceholderIndex() const; + void setDropPlaceholderIndex(int index); + + public Q_SLOTS: + void reset(); + + Q_SIGNALS: + void sourceModelChanged() const; + void dropPlaceholderIndexChanged(); + + protected: + void inhibitTriggering(); + + private: + QModelIndex indexToSourceIndex(const QModelIndex &index) const; + QModelIndex sourceIndexToIndex(const QModelIndex &index) const; + int sourceRowToRow(int sourceRow) const; + int rowToSourceRow(int row) const; + + void connectSignals(); + void disconnectSignals(); + + QPointer m_sourceModel; + + int m_dropPlaceholderIndex; + bool m_isTriggerInhibited; + QTimer m_triggerInhibitor; +}; + +#endif diff --git a/applets/kicker/plugin/placeholdermodel.cpp b/applets/kicker/plugin/placeholdermodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/placeholdermodel.cpp @@ -0,0 +1,407 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * Copyright (C) 2017 by Ivan Cukic * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "placeholdermodel.h" +#include "actionlist.h" +#include "debug.h" + +#include + +PlaceholderModel::PlaceholderModel(QObject *parent) + : AbstractModel(parent) + , m_dropPlaceholderIndex(-1) + , m_isTriggerInhibited(false) +{ + connect(&m_triggerInhibitor, &QTimer::timeout, + this, [&] { + qCDebug(KICKER_DEBUG) << "%%% Inhibit stopped"; + m_isTriggerInhibited = false; + }); + + m_triggerInhibitor.setInterval(500); + m_triggerInhibitor.setSingleShot(true); +} + +void PlaceholderModel::inhibitTriggering() +{ + qCDebug(KICKER_DEBUG) << "%%% Inhibit started"; + m_isTriggerInhibited = true; + m_triggerInhibitor.start(); +} + +PlaceholderModel::~PlaceholderModel() +{ +} + +QString PlaceholderModel::description() const +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->description(); + + } else { + return QString(); + } +} + +QAbstractItemModel *PlaceholderModel::sourceModel() const +{ + return m_sourceModel; +} + +void PlaceholderModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + disconnectSignals(); + + beginResetModel(); + + m_sourceModel = sourceModel; + + connectSignals(); + + endResetModel(); + + emit countChanged(); + emit sourceModelChanged(); + emit descriptionChanged(); +} + +bool PlaceholderModel::canFetchMore(const QModelIndex &parent) const +{ + return m_sourceModel && m_sourceModel->canFetchMore(indexToSourceIndex(parent)); +} + +void PlaceholderModel::fetchMore(const QModelIndex &parent) +{ + if (m_sourceModel) { + m_sourceModel->fetchMore(indexToSourceIndex(parent)); + } +} + +QModelIndex PlaceholderModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return m_sourceModel ? createIndex(row, column) + : QModelIndex(); +} + +QModelIndex PlaceholderModel::parent(const QModelIndex &index) const +{ + Q_UNUSED(index) + + return QModelIndex(); +} + +QVariant PlaceholderModel::data(const QModelIndex &index, int role) const +{ + const auto row = index.row(); + + if (m_dropPlaceholderIndex == row) { + switch (role) { + case Kicker::IsDropPlaceholderRole: + return true; + + // TODO: Maybe it would be nice to show something here? + // case Qt::DisplayRole: + // return "placeholder"; + // + // case Qt::DecorationRole: + // return "select"; + + default: + return QVariant(); + + } + } + + return m_sourceModel ? m_sourceModel->data(indexToSourceIndex(index), role) + : QVariant(); +} + +int PlaceholderModel::rowCount(const QModelIndex &parent) const +{ + if (!m_sourceModel || parent.isValid()) { + return 0; + } + + return m_sourceModel->rowCount() + + (m_dropPlaceholderIndex != -1 ? 1 : 0); +} + +QModelIndex PlaceholderModel::indexToSourceIndex(const QModelIndex& index) const +{ + if (!m_sourceModel || !index.isValid()) { + return QModelIndex(); + } + + const auto row = index.row(); + const auto column = index.column(); + + return index.parent().isValid() ? + // We do not support tree models + QModelIndex() : + + // If we are on top-level, lets add a placeholder + m_sourceModel->index( + row - (m_dropPlaceholderIndex != -1 && row > m_dropPlaceholderIndex ? 1 : 0), + column, + QModelIndex() + ); +} + +int PlaceholderModel::sourceRowToRow(int sourceRow) const +{ + return sourceRow + + (m_dropPlaceholderIndex != -1 && sourceRow >= m_dropPlaceholderIndex ? 1 : 0); +} + +int PlaceholderModel::rowToSourceRow(int row) const +{ + return row == m_dropPlaceholderIndex ? -1 : + row - (m_dropPlaceholderIndex != -1 && row > m_dropPlaceholderIndex ? 1 : 0); +} + +QModelIndex PlaceholderModel::sourceIndexToIndex(const QModelIndex& sourceIndex) const +{ + if (!m_sourceModel || !sourceIndex.isValid()) { + return QModelIndex(); + } + + const auto sourceRow = sourceIndex.row(); + const auto sourceColumn = sourceIndex.column(); + + return sourceIndex.parent().isValid() ? + // We do not support tree-models + QModelIndex() : + + // If we are on top-level, lets add a placeholder + index( + sourceRowToRow(sourceRow), + sourceColumn, + QModelIndex() + ); +} + +bool PlaceholderModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (m_isTriggerInhibited) return false; + + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->trigger(rowToSourceRow(row), actionId, argument); + + } else { + return false; + } +} + +QString PlaceholderModel::labelForRow(int row) +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->labelForRow(rowToSourceRow(row)); + + } else { + return QString(); + } + +} + +AbstractModel* PlaceholderModel::modelForRow(int row) +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->modelForRow(rowToSourceRow(row)); + + } else { + return nullptr; + } +} + +AbstractModel* PlaceholderModel::favoritesModel() +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->favoritesModel(); + + } else { + return AbstractModel::favoritesModel(); + } +} + +int PlaceholderModel::separatorCount() const +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->separatorCount(); + + } else { + return 0; + } +} + +void PlaceholderModel::reset() +{ + emit beginResetModel(); + emit endResetModel(); + emit countChanged(); + emit separatorCountChanged(); +} + +void PlaceholderModel::connectSignals() +{ + if (!m_sourceModel) { + return; + } + + const auto sourceModelPtr = m_sourceModel.data(); + + connect(sourceModelPtr, SIGNAL(destroyed()), this, SLOT(reset())); + + connect(sourceModelPtr, &QAbstractItemModel::dataChanged, + this, [this] (const QModelIndex &from, const QModelIndex &to, const QVector &roles) { + emit dataChanged(sourceIndexToIndex(from), + sourceIndexToIndex(to), + roles); + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsAboutToBeInserted, + this, [this] (const QModelIndex &parent, int from, int to) { + if (parent.isValid()) { + qWarning() << "We do not support tree models"; + + } else { + beginInsertRows(QModelIndex(), + sourceRowToRow(from), + sourceRowToRow(to)); + + } + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsInserted, + this, [this] { + endInsertRows(); + emit countChanged(); + }); + + + connect(sourceModelPtr, &QAbstractItemModel::rowsAboutToBeMoved, + this, [this] (const QModelIndex &source, int from, int to, const QModelIndex &dest, int destRow) { + if (source.isValid() || dest.isValid()) { + qWarning() << "We do not support tree models"; + + } else { + beginMoveRows(QModelIndex(), + sourceRowToRow(from), + sourceRowToRow(to), + QModelIndex(), + sourceRowToRow(destRow)); + } + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsMoved, + this, [this] { + endMoveRows(); + }); + + + connect(sourceModelPtr, &QAbstractItemModel::rowsAboutToBeRemoved, + this, [this] (const QModelIndex &parent, int from, int to) { + if (parent.isValid()) { + qWarning() << "We do not support tree models"; + + } else { + beginRemoveRows(QModelIndex(), + sourceRowToRow(from), + sourceRowToRow(to)); + } + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsRemoved, + this, [this] { + endRemoveRows(); + emit countChanged(); + }); + + + connect(sourceModelPtr, &QAbstractItemModel::modelAboutToBeReset, + this, [this] { + beginResetModel(); + }); + + connect(sourceModelPtr, &QAbstractItemModel::modelReset, + this, [this] { + endResetModel(); + emit countChanged(); + }); + + // We do not have persistant indices + // connect(sourceModelPtr, &QAbstractItemModel::layoutAboutToBeChanged), + // this, &PlaceholderModel::layoutAboutToBeChanged); + // connect(sourceModelPtr, &QAbstractItemModel::layoutChanged), + // this, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), + // Qt::UniqueConnection); +} + +void PlaceholderModel::disconnectSignals() +{ + if (!m_sourceModel) { + return; + } + + disconnect(m_sourceModel, nullptr, this, nullptr); +} + +int PlaceholderModel::dropPlaceholderIndex() const +{ + return m_dropPlaceholderIndex; +} + +void PlaceholderModel::setDropPlaceholderIndex(int index) +{ + if (index == m_dropPlaceholderIndex) return; + + inhibitTriggering(); + + if (index == -1 && m_dropPlaceholderIndex != -1) { + // Removing the placeholder + beginRemoveRows(QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex); + m_dropPlaceholderIndex = index; + endRemoveRows(); + + emit countChanged(); + + } else if (index != -1 && m_dropPlaceholderIndex == -1) { + // Creating the placeholder + beginInsertRows(QModelIndex(), index, index); + m_dropPlaceholderIndex = index; + endInsertRows(); + + emit countChanged(); + + } else if (m_dropPlaceholderIndex != index) { + // Moving the placeholder + int modelTo = index + (index > m_dropPlaceholderIndex ? 1 : 0); + + if (beginMoveRows( + QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex, + QModelIndex(), modelTo)) { + m_dropPlaceholderIndex = index; + endMoveRows(); + } + } + + emit dropPlaceholderIndexChanged(); +} diff --git a/dataengines/share/packagestructure/share_package.h b/applets/kicker/plugin/processrunner.h copy from dataengines/share/packagestructure/share_package.h copy to applets/kicker/plugin/processrunner.h --- a/dataengines/share/packagestructure/share_package.h +++ b/applets/kicker/plugin/processrunner.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2013 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,16 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_PACKAGE_H -#define SHARE_PACKAGE_H +#ifndef PROCESSRUNNER_H +#define PROCESSRUNNER_H -#include +#include -class SharePackage : public KPackage::PackageStructure +class ProcessRunner : public QObject { -public: - explicit SharePackage(QObject *parent = nullptr, QVariantList args = QVariantList()); - void initPackage(KPackage::Package *package) override; + Q_OBJECT + + public: + explicit ProcessRunner(QObject *parent = nullptr); + ~ProcessRunner() override; + + Q_INVOKABLE void runMenuEditor(); }; #endif diff --git a/dataengines/share/backends/pastebincom/contents/code/main.js b/applets/kicker/plugin/processrunner.cpp rename from dataengines/share/backends/pastebincom/contents/code/main.js rename to applets/kicker/plugin/processrunner.cpp --- a/dataengines/share/backends/pastebincom/contents/code/main.js +++ b/applets/kicker/plugin/processrunner.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010 by Artur Duque de Souza * + * Copyright (C) 2013 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,23 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -function url() { - return "http://pastebin.com/api_public.php"; -} +#include "processrunner.h" -function contentKey() { - return "paste_code"; -} +#include -function setup() { - provider.addQueryItem("paste_name", "Test"); +ProcessRunner::ProcessRunner(QObject *parent) : QObject(parent) +{ } -function handleResultData(data) { - if (data.search("ERROR") != -1) { - provider.error(data); - return; - } - provider.success(data); +ProcessRunner::~ProcessRunner() +{ } +void ProcessRunner::runMenuEditor() +{ + KProcess::startDetached(QStringLiteral("kmenuedit")); +} diff --git a/applets/kicker/plugin/qmldir b/applets/kicker/plugin/qmldir new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/qmldir @@ -0,0 +1,2 @@ +module org.kde.plasma.private.kicker +plugin kickerplugin diff --git a/dataengines/share/shareservice.h b/applets/kicker/plugin/recentcontactsmodel.h copy from dataengines/share/shareservice.h copy to applets/kicker/plugin/recentcontactsmodel.h --- a/dataengines/share/shareservice.h +++ b/applets/kicker/plugin/recentcontactsmodel.h @@ -1,5 +1,6 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014-2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,54 +18,42 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_SERVICE_H -#define SHARE_SERVICE_H +#ifndef RECENTCONTACTSMODEL_H +#define RECENTCONTACTSMODEL_H -#include "shareengine.h" +#include "forwardingmodel.h" -#include -#include -#include - -class ShareProvider; - -namespace Plasma { - class ServiceJob; +namespace KPeople { + class PersonData; } -namespace KJSEmbed { - class Engine; -} - -class ShareService : public Plasma::Service +class RecentContactsModel : public ForwardingModel { Q_OBJECT -public: - explicit ShareService(ShareEngine *engine); - Plasma::ServiceJob *createJob(const QString &operation, - QMap ¶meters) override; -}; + public: + explicit RecentContactsModel(QObject *parent = nullptr); + ~RecentContactsModel() override; -class ShareJob : public Plasma::ServiceJob -{ - Q_OBJECT + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + bool hasActions() const override; + QVariantList actions() const override; -public: - ShareJob(const QString &destination, const QString &operation, - QMap ¶meters, QObject *parent = nullptr); - ~ShareJob() override; - void start() override; + private Q_SLOTS: + void refresh() override; + void buildCache(); + void personDataChanged(); -public Q_SLOTS: - void publish(); - void showResult(const QString &url); - void showError(const QString &msg); + private: + void insertPersonData(const QString &id, int row); -private: - QScopedPointer m_engine; - ShareProvider *m_provider; - KPackage::Package m_package; + QHash m_idToData; + QHash m_dataToRow; }; -#endif // SHARE_SERVICE +#endif diff --git a/applets/kicker/plugin/recentcontactsmodel.cpp b/applets/kicker/plugin/recentcontactsmodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/recentcontactsmodel.cpp @@ -0,0 +1,246 @@ +/*************************************************************************** + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "recentcontactsmodel.h" +#include "actionlist.h" +#include "contactentry.h" + +#include + +#include + +#include +#include + +#include //FIXME TODO: Pretty include in KPeople broken. +#include +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +RecentContactsModel::RecentContactsModel(QObject *parent) : ForwardingModel(parent) +{ + refresh(); +} + +RecentContactsModel::~RecentContactsModel() +{ +} + +QString RecentContactsModel::description() const +{ + return i18n("Contacts"); +} + +QVariant RecentContactsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + QString id = sourceModel()->data(index, ResultModel::ResourceRole).toString(); + + KPeople::PersonData *data = nullptr; + + if (m_idToData.contains(id)) { + data = m_idToData[id]; + } + + if (!data) { + const_cast(this)->insertPersonData(id, index.row()); + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return data->name(); + } else if (role == Qt::DecorationRole) { + return data->presenceIconName(); + } else if (role == Kicker::FavoriteIdRole) { + return id; + } else if (role == Kicker::HasActionListRole) { + return true; + } else if (role == Kicker::ActionListRole) { + QVariantList actionList ; + + const QVariantMap &forgetAction = Kicker::createActionItem(i18n("Forget Contact"), QStringLiteral("edit-clear-history"), QStringLiteral("forget")); + actionList << forgetAction; + + const QVariantMap &forgetAllAction = Kicker::createActionItem(i18n("Forget All Contacts"), QStringLiteral("edit-clear-history"), QStringLiteral("forgetAll")); + actionList << forgetAllAction; + + actionList << Kicker::createSeparatorActionItem(); + + actionList << Kicker::createActionItem(i18n("Show Contact Information..."), QStringLiteral("identity"), QStringLiteral("showContactInfo")); + + return actionList; + } else if (role == Kicker::DescriptionRole) { + return QString(); + } + + return QVariant(); +} + +bool RecentContactsModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + Q_UNUSED(argument) + + bool withinBounds = row >= 0 && row < rowCount(); + + if (actionId.isEmpty() && withinBounds) { + QString id = sourceModel()->data(sourceModel()->index(row, 0), ResultModel::ResourceRole).toString(); + + const QList actionList = KPeople::actionsForPerson(id, this); + + if (!actionList.isEmpty()) { + QAction *chat = nullptr; + + foreach (QAction *action, actionList) { + const QVariant &actionType = action->property("actionType"); + + if (!actionType.isNull() && actionType.toInt() == KPeople::ActionType::TextChatAction) { + chat = action; + } + } + + if (chat) { + chat->trigger(); + + return true; + } + } + + return false; + } else if (actionId == QLatin1String("showContactInfo") && withinBounds) { + ContactEntry::showPersonDetailsDialog(sourceModel()->data(sourceModel()->index(row, 0), + ResultModel::ResourceRole).toString()); + } else if (actionId == QLatin1String("forget") && withinBounds) { + if (sourceModel()) { + ResultModel *resultModel = static_cast(sourceModel()); + resultModel->forgetResource(row); + } + + return false; + } else if (actionId == QLatin1String("forgetAll")) { + if (sourceModel()) { + ResultModel *resultModel = static_cast(sourceModel()); + resultModel->forgetAllResources(); + } + + return false; + } + + return false; +} + +bool RecentContactsModel::hasActions() const +{ + return rowCount(); +} + +QVariantList RecentContactsModel::actions() const +{ + QVariantList actionList; + + if (rowCount()) { + actionList << Kicker::createActionItem(i18n("Forget All Contacts"), QStringLiteral("edit-clear-history"), QStringLiteral("forgetAll")); + } + + return actionList; +} + +void RecentContactsModel::refresh() +{ + QObject *oldModel = sourceModel(); + + auto query = UsedResources + | RecentlyUsedFirst + | Agent(QStringLiteral("KTp")) + | Type::any() + | Activity::current() + | Url::startsWith(QStringLiteral("ktp")) + | Limit(15); + + ResultModel *model = new ResultModel(query); + + QModelIndex index; + + if (model->canFetchMore(index)) { + model->fetchMore(index); + } + + // FIXME TODO: Don't wipe entire cache on transactions. + connect(model, &QAbstractItemModel::rowsInserted, + this, &RecentContactsModel::buildCache, Qt::UniqueConnection); + connect(model, &QAbstractItemModel::rowsRemoved, + this, &RecentContactsModel::buildCache, Qt::UniqueConnection); + connect(model, &QAbstractItemModel::rowsMoved, + this, &RecentContactsModel::buildCache, Qt::UniqueConnection); + connect(model, &QAbstractItemModel::modelReset, + this, &RecentContactsModel::buildCache, Qt::UniqueConnection); + + setSourceModel(model); + + buildCache(); + + delete oldModel; +} + +void RecentContactsModel::buildCache() +{ + qDeleteAll(m_idToData); + m_idToData.clear(); + m_dataToRow.clear(); + + QString id; + + for(int i = 0; i < sourceModel()->rowCount(); ++i) { + id = sourceModel()->data(sourceModel()->index(i, 0), ResultModel::ResourceRole).toString(); + + if (!m_idToData.contains(id)) { + insertPersonData(id, i); + } + } +} + +void RecentContactsModel::insertPersonData(const QString& id, int row) +{ + KPeople::PersonData *data = new KPeople::PersonData(id); + + m_idToData[id] = data; + m_dataToRow[data] = row; + + connect(data, &KPeople::PersonData::dataChanged, this, &RecentContactsModel::personDataChanged); +} + +void RecentContactsModel::personDataChanged() +{ + KPeople::PersonData *data = static_cast(sender()); + + if (m_dataToRow.contains(data)) { + int row = m_dataToRow[data]; + + QModelIndex idx = sourceModel()->index(row, 0); + + emit dataChanged(idx, idx); + } +} diff --git a/applets/kicker/plugin/recentusagemodel.h b/applets/kicker/plugin/recentusagemodel.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/recentusagemodel.h @@ -0,0 +1,117 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef RECENTUSAGEMODEL_H +#define RECENTUSAGEMODEL_H + +#include "forwardingmodel.h" + +#include +#include +#include + +class GroupSortProxy : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + explicit GroupSortProxy(AbstractModel *parentModel, QAbstractItemModel *sourceModel); + ~GroupSortProxy() override; + + protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; +}; + +class InvalidAppsFilterProxy : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + explicit InvalidAppsFilterProxy(AbstractModel *parentModel, QAbstractItemModel *sourceModel); + ~InvalidAppsFilterProxy() override; + + protected: + bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + + private Q_SLOTS: + void connectNewFavoritesModel(); + + private: + QPointer m_parentModel; +}; + +class RecentUsageModel : public ForwardingModel, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(int ordering READ ordering WRITE setOrdering NOTIFY orderingChanged) + + public: + enum IncludeUsage { AppsAndDocs, OnlyApps, OnlyDocs }; + enum Ordering { Recent, Popular }; + + explicit RecentUsageModel( + QObject *parent = nullptr, + IncludeUsage usage = AppsAndDocs, + int ordering = Recent); + ~RecentUsageModel() override; + + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + bool hasActions() const override; + QVariantList actions() const override; + + IncludeUsage usage() const; + + void setOrdering(int ordering); + int ordering() const; + + void classBegin() override; + void componentComplete() override; + + Q_SIGNALS: + void orderingChanged(int ordering); + + private Q_SLOTS: + void refresh() override; + + private: + QVariant appData(const QString &resource, int role) const; + QVariant docData(const QString &resource, int role) const; + + QString resourceAt(int row) const; + + QString forgetAllActionName() const; + + IncludeUsage m_usage; + QPointer m_activitiesModel; + + Ordering m_ordering; + + bool m_complete; + KFilePlacesModel *m_placesModel; +}; + +#endif diff --git a/applets/kicker/plugin/recentusagemodel.cpp b/applets/kicker/plugin/recentusagemodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/recentusagemodel.cpp @@ -0,0 +1,528 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "recentusagemodel.h" +#include "actionlist.h" +#include "appsmodel.h" +#include "appentry.h" +#include "kastatsfavoritesmodel.h" +#include + +#include + +#include +#include +#include +#include +#include +#if HAVE_X11 +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +GroupSortProxy::GroupSortProxy(AbstractModel *parentModel, QAbstractItemModel *sourceModel) : QSortFilterProxyModel(parentModel) +{ + sourceModel->setParent(this); + setSourceModel(sourceModel); + sort(0); +} + +GroupSortProxy::~GroupSortProxy() +{ +} + +InvalidAppsFilterProxy::InvalidAppsFilterProxy(AbstractModel *parentModel, QAbstractItemModel *sourceModel) : QSortFilterProxyModel(parentModel) +, m_parentModel(parentModel) +{ + connect(parentModel, &AbstractModel::favoritesModelChanged, this, &InvalidAppsFilterProxy::connectNewFavoritesModel); + connectNewFavoritesModel(); + + sourceModel->setParent(this); + setSourceModel(sourceModel); +} + +InvalidAppsFilterProxy::~InvalidAppsFilterProxy() +{ +} + +void InvalidAppsFilterProxy::connectNewFavoritesModel() +{ + KAStatsFavoritesModel* favoritesModel = static_cast(m_parentModel->favoritesModel()); + connect(favoritesModel, &KAStatsFavoritesModel::favoritesChanged, this, &QSortFilterProxyModel::invalidate); + + invalidate(); +} + +bool InvalidAppsFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent); + + const QString resource = sourceModel()->index(source_row, 0).data(ResultModel::ResourceRole).toString(); + + if (resource.startsWith(QLatin1String("applications:"))) { + KService::Ptr service = KService::serviceByStorageId(resource.section(QLatin1Char(':'), 1)); + + KAStatsFavoritesModel* favoritesModel = m_parentModel ? static_cast(m_parentModel->favoritesModel()) : nullptr; + + return (service && (!favoritesModel || !favoritesModel->isFavorite(service->storageId()))); + } + + return true; +} + +bool InvalidAppsFilterProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + return (left.row() < right.row()); +} + +bool GroupSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + const QString &lResource = sourceModel()->data(left, ResultModel::ResourceRole).toString(); + const QString &rResource = sourceModel()->data(right, ResultModel::ResourceRole).toString(); + + if (lResource.startsWith(QLatin1String("applications:")) + && !rResource.startsWith(QLatin1String("applications:"))) { + return true; + } else if (!lResource.startsWith(QLatin1String("applications:")) + && rResource.startsWith(QLatin1String("applications:"))) { + return false; + } + + return (left.row() < right.row()); +} + +RecentUsageModel::RecentUsageModel(QObject *parent, IncludeUsage usage, int ordering) +: ForwardingModel(parent) +, m_usage(usage) +, m_ordering((Ordering)ordering) +, m_complete(false) +, m_placesModel(new KFilePlacesModel(this)) +{ + refresh(); +} + +RecentUsageModel::~RecentUsageModel() +{ +} + +RecentUsageModel::IncludeUsage RecentUsageModel::usage() const +{ + return m_usage; +} + +QString RecentUsageModel::description() const +{ + switch (m_usage) { + case AppsAndDocs: + return i18n("Recently Used"); + case OnlyApps: + return i18n("Applications"); + case OnlyDocs: + default: + return i18n("Documents"); + } +} + +QString RecentUsageModel::resourceAt(int row) const +{ + QSortFilterProxyModel *sourceProxy = qobject_cast(sourceModel()); + + if (sourceProxy) { + return sourceProxy->sourceModel()->data(sourceProxy->mapToSource(sourceProxy->index(row, 0)), + ResultModel::ResourceRole).toString(); + } + + return sourceModel()->data(index(row, 0), ResultModel::ResourceRole).toString(); +} + +QVariant RecentUsageModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + const QString &resource = resourceAt(index.row()); + + if (resource.startsWith(QLatin1String("applications:"))) { + return appData(resource, role); + } else { + return docData(resource, role); + } +} + +QVariant RecentUsageModel::appData(const QString &resource, int role) const +{ + const QString storageId = resource.section(QLatin1Char(':'), 1); + KService::Ptr service = KService::serviceByStorageId(storageId); + + QStringList allowedTypes({ QLatin1String("Service"), QLatin1String("Application") }); + + if (!service || !allowedTypes.contains(service->property(QLatin1String("Type")).toString()) + || service->exec().isEmpty()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + AppsModel *parentModel = qobject_cast(QObject::parent()); + + if (parentModel) { + return AppEntry::nameFromService(service, + (AppEntry::NameFormat)qobject_cast(QObject::parent())->appNameFormat()); + } else { + return AppEntry::nameFromService(service, AppEntry::NameOnly); + } + } else if (role == Qt::DecorationRole) { + return QIcon::fromTheme(service->icon(), QIcon::fromTheme(QStringLiteral("unknown"))); + } else if (role == Kicker::DescriptionRole) { + return service->comment(); + } else if (role == Kicker::GroupRole) { + return i18n("Applications"); + } else if (role == Kicker::FavoriteIdRole) { + return service->storageId(); + } else if (role == Kicker::HasActionListRole) { + return true; + } else if (role == Kicker::ActionListRole) { + QVariantList actionList; + + const QVariantList &jumpList = Kicker::jumpListActions(service); + if (!jumpList.isEmpty()) { + actionList << jumpList << Kicker::createSeparatorActionItem(); + } + + const QVariantList &recentDocuments = Kicker::recentDocumentActions(service); + if (!recentDocuments.isEmpty()) { + actionList << recentDocuments << Kicker::createSeparatorActionItem(); + } + + const QVariantMap &forgetAction = Kicker::createActionItem(i18n("Forget Application"), QStringLiteral("edit-clear-history"), QStringLiteral("forget")); + actionList << forgetAction; + + const QVariantMap &forgetAllAction = Kicker::createActionItem(forgetAllActionName(), QStringLiteral("edit-clear-history"), QStringLiteral("forgetAll")); + actionList << forgetAllAction; + + return actionList; + } + + return QVariant(); +} + +QVariant RecentUsageModel::docData(const QString &resource, int role) const +{ + QUrl url(resource); + + if (url.scheme().isEmpty()) { + url.setScheme(QStringLiteral("file")); + } + + auto getFileItem = [=] () { + // Avoid calling QT_LSTAT and accessing recent documents + return KFileItem(url, KFileItem::SkipMimeTypeFromContent); + }; + + if (!url.isValid()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + auto fileItem = getFileItem(); + const auto index = m_placesModel->closestItem(fileItem.url()); + if (index.isValid()) { + const auto parentUrl = m_placesModel->url(index); + if (parentUrl == fileItem.url()) { + return m_placesModel->text(index); + } + } + return fileItem.text(); + } else if (role == Qt::DecorationRole) { + auto fileItem = getFileItem(); + return QIcon::fromTheme(fileItem.iconName(), QIcon::fromTheme(QStringLiteral("unknown"))); + } else if (role == Kicker::GroupRole) { + return i18n("Documents"); + } else if (role == Kicker::FavoriteIdRole || role == Kicker::UrlRole) { + return url.toString(); + } else if (role == Kicker::DescriptionRole) { + auto fileItem = getFileItem(); + QString desc = fileItem.localPath(); + + const auto index = m_placesModel->closestItem(fileItem.url()); + if (index.isValid()) { + // the current file has a parent in placesModel + const auto parentUrl = m_placesModel->url(index); + if (parentUrl == fileItem.url()) { + // if the current item is a place + return QString(); + } + desc.truncate(desc.lastIndexOf(QChar('/'))); + const auto text = m_placesModel->text(index); + desc.replace(0, parentUrl.path().length(), text); + } else { + // remove filename + desc.truncate(desc.lastIndexOf(QChar('/'))); + } + return desc; + } else if (role == Kicker::UrlRole) { + return url; + } else if (role == Kicker::HasActionListRole) { + return true; + } else if (role == Kicker::ActionListRole) { + auto fileItem = getFileItem(); + QVariantList actionList = Kicker::createActionListForFileItem(fileItem); + + actionList << Kicker::createSeparatorActionItem(); + + QVariantMap openParentFolder = Kicker::createActionItem(i18n("Open Containing Folder"), QStringLiteral("folder-open"), QStringLiteral("openParentFolder")); + actionList << openParentFolder; + + QVariantMap forgetAction = Kicker::createActionItem(i18n("Forget Document"), QStringLiteral("edit-clear-history"), QStringLiteral("forget")); + actionList << forgetAction; + + QVariantMap forgetAllAction = Kicker::createActionItem(forgetAllActionName(), QStringLiteral("edit-clear-history"), QStringLiteral("forgetAll")); + actionList << forgetAllAction; + + return actionList; + } + + return QVariant(); +} + +bool RecentUsageModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + Q_UNUSED(argument) + + bool withinBounds = row >= 0 && row < rowCount(); + + if (actionId.isEmpty() && withinBounds) { + const QString &resource = resourceAt(row); + + if (!resource.startsWith(QLatin1String("applications:"))) { + const QUrl resourceUrl = docData(resource, Kicker::UrlRole).toUrl(); + const QList urlsList{resourceUrl}; + + QMimeDatabase db; + QMimeType mime = db.mimeTypeForUrl(resourceUrl); + KService::Ptr service = KMimeTypeTrader::self()->preferredService(mime.name()); + if (service) { + KRun::runApplication(*service, urlsList, nullptr); + } else { + QTimer::singleShot(0, [urlsList] { + KRun::displayOpenWithDialog(urlsList, nullptr); + }); + } + + return true; + } + + const QString storageId = resource.section(QLatin1Char(':'), 1); + KService::Ptr service = KService::serviceByStorageId(storageId); + + if (!service) { + return false; + } + + quint32 timeStamp = 0; + +#if HAVE_X11 + if (QX11Info::isPlatformX11()) { + timeStamp = QX11Info::appUserTime(); + } +#endif + + // TODO Once we depend on KDE Frameworks 5.24 and D1902 is merged, use KRun::runApplication instead + KRun::runService(*service, {}, nullptr, true, {}, KStartupInfo::createNewStartupIdForTimestamp(timeStamp)); + + KActivities::ResourceInstance::notifyAccessed(QUrl(QStringLiteral("applications:") + storageId), + QStringLiteral("org.kde.plasma.kicker")); + + return true; + } else if (actionId == QLatin1String("forget") && withinBounds) { + if (m_activitiesModel) { + QModelIndex idx = sourceModel()->index(row, 0); + QSortFilterProxyModel *sourceProxy = qobject_cast(sourceModel()); + + while (sourceProxy) { + idx = sourceProxy->mapToSource(idx); + sourceProxy = qobject_cast(sourceProxy->sourceModel()); + } + + static_cast(m_activitiesModel.data())->forgetResource(idx.row()); + } + + return false; + } else if (actionId == QLatin1String("openParentFolder") && withinBounds) { + const auto url = QUrl::fromUserInput(resourceAt(row)); + KIO::highlightInFileManager({url}); + } else if (actionId == QLatin1String("forgetAll")) { + if (m_activitiesModel) { + static_cast(m_activitiesModel.data())->forgetAllResources(); + } + + return false; + } else if (withinBounds) { + const QString &resource = resourceAt(row); + + if (resource.startsWith(QLatin1String("applications:"))) { + const QString storageId = sourceModel()->data(sourceModel()->index(row, 0), + ResultModel::ResourceRole).toString().section(QLatin1Char(':'), 1); + KService::Ptr service = KService::serviceByStorageId(storageId); + + if (service) { + return Kicker::handleRecentDocumentAction(service, actionId, argument); + } + } else { + bool close = false; + + QUrl url(sourceModel()->data(sourceModel()->index(row, 0), ResultModel::ResourceRole).toString()); + + KFileItem item(url); + + if (Kicker::handleFileItemAction(item, actionId, argument, &close)) { + return close; + } + } + } + + return false; +} + +bool RecentUsageModel::hasActions() const +{ + return rowCount(); +} + +QVariantList RecentUsageModel::actions() const +{ + QVariantList actionList; + + if (rowCount()) { + actionList << Kicker::createActionItem(forgetAllActionName(), QStringLiteral("edit-clear-history"), QStringLiteral("forgetAll")); + } + + return actionList; +} + +QString RecentUsageModel::forgetAllActionName() const +{ + switch (m_usage) { + case AppsAndDocs: + return i18n("Forget All"); + case OnlyApps: + return i18n("Forget All Applications"); + case OnlyDocs: + default: + return i18n("Forget All Documents"); + } +} + +void RecentUsageModel::setOrdering(int ordering) +{ + if (ordering == m_ordering) return; + + m_ordering = (Ordering)ordering; + refresh(); + + emit orderingChanged(ordering); +} + +int RecentUsageModel::ordering() const +{ + return m_ordering; +} + +void RecentUsageModel::classBegin() +{ +} + +void RecentUsageModel::componentComplete() +{ + m_complete = true; + + refresh(); +} + +void RecentUsageModel::refresh() +{ + if (qmlEngine(this) && !m_complete) { + return; + } + + QAbstractItemModel *oldModel = sourceModel(); + disconnectSignals(); + setSourceModel(nullptr); + delete oldModel; + + auto query = UsedResources + | (m_ordering == Recent ? RecentlyUsedFirst : HighScoredFirst) + | Agent::any() + | Type::any() + | Activity::current(); + + switch (m_usage) { + case AppsAndDocs: + { + query = query | Url::startsWith(QStringLiteral("applications:")) | Url::file() | Limit(30); + break; + } + case OnlyApps: + { + query = query | Url::startsWith(QStringLiteral("applications:")) | Limit(15); + break; + } + case OnlyDocs: + default: + { + query = query | Url::file() | Limit(15); + } + } + + m_activitiesModel = new ResultModel(query); + QAbstractItemModel *model = m_activitiesModel; + + QModelIndex index; + + if (model->canFetchMore(index)) { + model->fetchMore(index); + } + + if (m_usage != OnlyDocs) { + model = new InvalidAppsFilterProxy(this, model); + } + + if (m_usage == AppsAndDocs) { + model = new GroupSortProxy(this, model); + } + + setSourceModel(model); +} diff --git a/applets/kicker/plugin/rootmodel.h b/applets/kicker/plugin/rootmodel.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/rootmodel.h @@ -0,0 +1,127 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef ROOTMODEL_H +#define ROOTMODEL_H + +#include "appsmodel.h" + +class KAStatsFavoritesModel; +class RecentContactsModel; +class RecentUsageModel; +class SystemModel; + +class RootModel; + +class GroupEntry : public AbstractGroupEntry +{ + public: + GroupEntry(AppsModel *parentModel, const QString &name, + const QString &iconName, AbstractModel *childModel); + + QIcon icon() const override; + QString name() const override; + + bool hasChildren() const override; + AbstractModel *childModel() const override; + + private: + QString m_name; + QString m_iconName; + QPointer m_childModel; +}; + +class RootModel : public AppsModel +{ + Q_OBJECT + + Q_PROPERTY(QObject* systemFavoritesModel READ systemFavoritesModel NOTIFY systemFavoritesModelChanged) + Q_PROPERTY(bool showAllApps READ showAllApps WRITE setShowAllApps NOTIFY showAllAppsChanged) + Q_PROPERTY(bool showAllAppsCategorized READ showAllAppsCategorized WRITE setShowAllAppsCategorized NOTIFY showAllAppsCategorizedChanged) + Q_PROPERTY(bool showRecentApps READ showRecentApps WRITE setShowRecentApps NOTIFY showRecentAppsChanged) + Q_PROPERTY(bool showRecentDocs READ showRecentDocs WRITE setShowRecentDocs NOTIFY showRecentDocsChanged) + Q_PROPERTY(bool showRecentContacts READ showRecentContacts WRITE setShowRecentContacts NOTIFY showRecentContactsChanged) + Q_PROPERTY(int recentOrdering READ recentOrdering WRITE setRecentOrdering NOTIFY recentOrderingChanged) + Q_PROPERTY(bool showPowerSession READ showPowerSession WRITE setShowPowerSession NOTIFY showPowerSessionChanged) + + public: + explicit RootModel(QObject *parent = nullptr); + ~RootModel() override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + bool showAllApps() const; + void setShowAllApps(bool show); + + bool showAllAppsCategorized() const; + void setShowAllAppsCategorized(bool showCategorized); + + bool showRecentApps() const; + void setShowRecentApps(bool show); + + bool showRecentDocs() const; + void setShowRecentDocs(bool show); + + bool showRecentContacts() const; + void setShowRecentContacts(bool show); + + int recentOrdering() const; + void setRecentOrdering(int ordering); + + bool showPowerSession() const; + void setShowPowerSession(bool show); + + AbstractModel* favoritesModel() override; + AbstractModel* systemFavoritesModel(); + + Q_SIGNALS: + void refreshed() const; + void systemFavoritesModelChanged() const; + void showAllAppsChanged() const; + void showAllAppsCategorizedChanged() const; + void showRecentAppsChanged() const; + void showRecentDocsChanged() const; + void showRecentContactsChanged() const; + void showPowerSessionChanged() const; + void recentOrderingChanged() const; + void recentAppsModelChanged() const; + + protected Q_SLOTS: + void refresh() override; + + private: + KAStatsFavoritesModel *m_favorites; + SystemModel *m_systemModel; + + bool m_showAllApps; + bool m_showAllAppsCategorized; + bool m_showRecentApps; + bool m_showRecentDocs; + bool m_showRecentContacts; + int m_recentOrdering; + bool m_showPowerSession; + + RecentUsageModel *m_recentAppsModel; + RecentUsageModel *m_recentDocsModel; + RecentContactsModel *m_recentContactsModel; +}; + +#endif diff --git a/applets/kicker/plugin/rootmodel.cpp b/applets/kicker/plugin/rootmodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/rootmodel.cpp @@ -0,0 +1,453 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "rootmodel.h" +#include "actionlist.h" +#include "kastatsfavoritesmodel.h" +#include "recentcontactsmodel.h" +#include "recentusagemodel.h" +#include "systemmodel.h" + +#include + +#include + +GroupEntry::GroupEntry(AppsModel *parentModel, const QString &name, + const QString &iconName, AbstractModel *childModel) +: AbstractGroupEntry(parentModel) +, m_name(name) +, m_iconName(iconName) +, m_childModel(childModel) +{ + QObject::connect(parentModel, &RootModel::cleared, childModel, &AbstractModel::deleteLater); + + QObject::connect(childModel, &AbstractModel::countChanged, + [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } + ); +} + +QString GroupEntry::name() const +{ + return m_name; +} + +QIcon GroupEntry::icon() const +{ + return QIcon::fromTheme(m_iconName, QIcon::fromTheme(QStringLiteral("unknown"))); +} + +bool GroupEntry::hasChildren() const +{ + return m_childModel && m_childModel->count() > 0; +} + +AbstractModel *GroupEntry::childModel() const +{ + return m_childModel; +} + +RootModel::RootModel(QObject *parent) : AppsModel(QString(), parent) +, m_favorites(new KAStatsFavoritesModel(this)) +, m_systemModel(nullptr) +, m_showAllApps(false) +, m_showAllAppsCategorized(false) +, m_showRecentApps(true) +, m_showRecentDocs(true) +, m_showRecentContacts(false) +, m_recentOrdering(RecentUsageModel::Recent) +, m_showPowerSession(true) +, m_recentAppsModel(nullptr) +, m_recentDocsModel(nullptr) +, m_recentContactsModel(nullptr) +{ +} + +RootModel::~RootModel() +{ +} + +QVariant RootModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || index.row() >= m_entryList.count()) { + return QVariant(); + } + + if (role == Kicker::HasActionListRole || role == Kicker::ActionListRole) { + const AbstractEntry *entry = m_entryList.at(index.row()); + + if (entry->type() == AbstractEntry::GroupType) { + const GroupEntry *group = static_cast(entry); + AbstractModel *model = group->childModel(); + + if (model == m_recentAppsModel + || model == m_recentDocsModel + || model == m_recentContactsModel) { + if (role == Kicker::HasActionListRole) { + return true; + } else if (role == Kicker::ActionListRole) { + QVariantList actionList; + actionList << model->actions(); + actionList << Kicker::createSeparatorActionItem(); + actionList << Kicker::createActionItem(i18n("Hide %1", + group->name()), QStringLiteral("view-hidden"), QStringLiteral("hideCategory")); + return actionList; + } + } + } + } + + return AppsModel::data(index, role); +} + +bool RootModel::trigger(int row, const QString& actionId, const QVariant& argument) +{ + const AbstractEntry *entry = m_entryList.at(row); + + if (entry->type() == AbstractEntry::GroupType) { + if (actionId == QLatin1String("hideCategory")) { + AbstractModel *model = entry->childModel(); + + if (model == m_recentAppsModel) { + setShowRecentApps(false); + + return true; + } else if (model == m_recentDocsModel) { + setShowRecentDocs(false); + + return true; + } else if (model == m_recentContactsModel) { + setShowRecentContacts(false); + + return true; + } + } else if (entry->childModel()->hasActions()) { + return entry->childModel()->trigger(-1, actionId, QVariant()); + } + } + + return AppsModel::trigger(row, actionId, argument); +} + +bool RootModel::showAllApps() const +{ + return m_showAllApps; +} + +void RootModel::setShowAllApps(bool show) +{ + if (m_showAllApps != show) { + m_showAllApps = show; + + refresh(); + + emit showAllAppsChanged(); + } +} + +bool RootModel::showAllAppsCategorized() const +{ + return m_showAllAppsCategorized; +} + +void RootModel::setShowAllAppsCategorized(bool showCategorized) +{ + if (m_showAllAppsCategorized != showCategorized) { + m_showAllAppsCategorized = showCategorized; + + refresh(); + + emit showAllAppsCategorizedChanged(); + } +} + +bool RootModel::showRecentApps() const +{ + return m_showRecentApps; +} + +void RootModel::setShowRecentApps(bool show) +{ + if (show != m_showRecentApps) { + m_showRecentApps = show; + + refresh(); + + emit showRecentAppsChanged(); + } +} + +bool RootModel::showRecentDocs() const +{ + return m_showRecentDocs; +} + +void RootModel::setShowRecentDocs(bool show) +{ + if (show != m_showRecentDocs) { + m_showRecentDocs = show; + + refresh(); + + emit showRecentDocsChanged(); + } +} + +bool RootModel::showRecentContacts() const +{ + return m_showRecentContacts; +} + +void RootModel::setShowRecentContacts(bool show) +{ + if (show != m_showRecentContacts) { + m_showRecentContacts = show; + + refresh(); + + emit showRecentContactsChanged(); + } +} + +int RootModel::recentOrdering() const +{ + return m_recentOrdering; +} + +void RootModel::setRecentOrdering(int ordering) +{ + if (ordering != m_recentOrdering) { + m_recentOrdering = ordering; + + refresh(); + + emit recentOrderingChanged(); + } +} + +bool RootModel::showPowerSession() const +{ + return m_showPowerSession; +} + +void RootModel::setShowPowerSession(bool show) +{ + if (show != m_showPowerSession) { + m_showPowerSession = show; + + refresh(); + + emit showPowerSessionChanged(); + } +} + +AbstractModel* RootModel::favoritesModel() +{ + return m_favorites; +} + +AbstractModel* RootModel::systemFavoritesModel() +{ + if (m_systemModel) { + return m_systemModel->favoritesModel(); + } + + return nullptr; +} + +void RootModel::refresh() +{ + if (!m_complete) { + return; + } + + beginResetModel(); + + AppsModel::refreshInternal(); + + AppsModel *allModel = nullptr; + m_recentAppsModel = nullptr; + m_recentDocsModel = nullptr; + m_recentContactsModel = nullptr; + + if (m_showAllApps) { + QHash appsHash; + + std::function processEntry = [&](AbstractEntry *entry) { + if (entry->type() == AbstractEntry::RunnableType) { + AppEntry *appEntry = static_cast(entry); + appsHash.insert(appEntry->service()->menuId(), appEntry); + } else if (entry->type() == AbstractEntry::GroupType) { + GroupEntry *groupEntry = static_cast(entry); + AbstractModel *model = groupEntry->childModel(); + + if (!model) { + return; + } + + for (int i = 0; i < model->count(); ++i) { + processEntry(static_cast(model->index(i, 0).internalPointer())); + } + } + }; + + for (AbstractEntry *entry : m_entryList) { + processEntry(entry); + } + + QList apps(appsHash.values()); + QCollator c; + + std::sort(apps.begin(), apps.end(), + [&c](AbstractEntry* a, AbstractEntry* b) { + if (a->type() != b->type()) { + return a->type() > b->type(); + } else { + return c.compare(a->name(), b->name()) < 0; + } + }); + + if (!m_showAllAppsCategorized && !m_paginate) { // The app list built above goes into a model. + allModel = new AppsModel(apps, false, this); + } else if (m_paginate) { // We turn the apps list into a subtree of pages. + m_favorites = new KAStatsFavoritesModel(this); + emit favoritesModelChanged(); + + QList groups; + + int at = 0; + QList page; + page.reserve(m_pageSize); + + foreach(AbstractEntry *app, apps) { + page.append(app); + + if (at == (m_pageSize - 1)) { + at = 0; + AppsModel *model = new AppsModel(page, false, this); + groups.append(new GroupEntry(this, QString(), QString(), model)); + page.clear(); + } else { + ++at; + } + } + + if (!page.isEmpty()) { + AppsModel *model = new AppsModel(page, false, this); + groups.append(new GroupEntry(this, QString(), QString(), model)); + } + + groups.prepend(new GroupEntry(this, QString(), QString(), m_favorites)); + + allModel = new AppsModel(groups, true, this); + } else { // We turn the apps list into a subtree of apps by starting letter. + QList groups; + QHash> m_categoryHash; + + foreach (const AbstractEntry *groupEntry, m_entryList) { + AbstractModel *model = groupEntry->childModel(); + + if (!model) continue; + + for (int i = 0; i < model->count(); ++i) { + AbstractEntry *appEntry = static_cast(model->index(i, 0).internalPointer()); + + if (appEntry->name().isEmpty()) { + continue; + } + + const QChar &first = appEntry->name().at(0).toUpper(); + m_categoryHash[first.isDigit() ? QStringLiteral("0-9") : first].append(appEntry); + } + } + + QHashIterator> i(m_categoryHash); + + while (i.hasNext()) { + i.next(); + AppsModel *model = new AppsModel(i.value(), false, this); + model->setDescription(i.key()); + groups.append(new GroupEntry(this, i.key(), QString(), model)); + } + + allModel = new AppsModel(groups, true, this); + } + + allModel->setDescription(QStringLiteral("KICKER_ALL_MODEL")); // Intentionally no i18n. + } + + int separatorPosition = 0; + + if (allModel) { + m_entryList.prepend(new GroupEntry(this, i18n("All Applications"), QString("applications-all"), allModel)); + ++separatorPosition; + } + + if (m_showRecentContacts) { + m_recentContactsModel = new RecentContactsModel(this); + m_entryList.prepend(new GroupEntry(this, i18n("Recent Contacts"), QString("view-history"), m_recentContactsModel)); + ++separatorPosition; + } + + if (m_showRecentDocs) { + m_recentDocsModel = new RecentUsageModel(this, RecentUsageModel::OnlyDocs, m_recentOrdering); + m_entryList.prepend(new GroupEntry(this, + m_recentOrdering == RecentUsageModel::Recent + ? i18n("Recent Documents") + : i18n("Often Used Documents"), + m_recentOrdering == RecentUsageModel::Recent + ? QString("view-history") + : QString("office-chart-pie"), + m_recentDocsModel)); + ++separatorPosition; + } + + if (m_showRecentApps) { + m_recentAppsModel = new RecentUsageModel(this, RecentUsageModel::OnlyApps, m_recentOrdering); + m_entryList.prepend(new GroupEntry(this, + m_recentOrdering == RecentUsageModel::Recent + ? i18n("Recent Applications") + : i18n("Often Used Applications"), + m_recentOrdering == RecentUsageModel::Recent + ? QString("view-history") + : QString("office-chart-pie"), + m_recentAppsModel)); + ++separatorPosition; + } + + if (m_showSeparators && separatorPosition > 0) { + m_entryList.insert(separatorPosition, new SeparatorEntry(this)); + ++m_separatorCount; + } + + m_systemModel = new SystemModel(this); + + if (m_showPowerSession) { + m_entryList << new GroupEntry(this, i18n("Power / Session"), QString("system-log-out"), m_systemModel); + } + + endResetModel(); + + m_favorites->refresh(); + + emit systemFavoritesModelChanged(); + emit countChanged(); + emit separatorCountChanged(); + + emit refreshed(); +} diff --git a/dataengines/share/shareservice.h b/applets/kicker/plugin/runnermatchesmodel.h copy from dataengines/share/shareservice.h copy to applets/kicker/plugin/runnermatchesmodel.h --- a/dataengines/share/shareservice.h +++ b/applets/kicker/plugin/runnermatchesmodel.h @@ -1,5 +1,6 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,54 +18,47 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_SERVICE_H -#define SHARE_SERVICE_H +#ifndef RUNNERMATCHESMODEL_H +#define RUNNERMATCHESMODEL_H -#include "shareengine.h" +#include "abstractmodel.h" -#include -#include -#include - -class ShareProvider; +#include namespace Plasma { - class ServiceJob; -} - -namespace KJSEmbed { - class Engine; + class RunnerManager; } -class ShareService : public Plasma::Service +class RunnerMatchesModel : public AbstractModel { Q_OBJECT -public: - explicit ShareService(ShareEngine *engine); - Plasma::ServiceJob *createJob(const QString &operation, - QMap ¶meters) override; -}; + Q_PROPERTY(QString name READ name CONSTANT) -class ShareJob : public Plasma::ServiceJob -{ - Q_OBJECT + public: + explicit RunnerMatchesModel(const QString &runnerId, const QString &name, + Plasma::RunnerManager *manager, QObject *parent = nullptr); + + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + QString runnerId() const { return m_runnerId; } + QString name() const { return m_name; } + + void setMatches(const QList &matches); + + AbstractModel* favoritesModel() override; -public: - ShareJob(const QString &destination, const QString &operation, - QMap ¶meters, QObject *parent = nullptr); - ~ShareJob() override; - void start() override; - -public Q_SLOTS: - void publish(); - void showResult(const QString &url); - void showError(const QString &msg); - -private: - QScopedPointer m_engine; - ShareProvider *m_provider; - KPackage::Package m_package; + private: + QString m_runnerId; + QString m_name; + Plasma::RunnerManager *m_runnerManager; + QList m_matches; }; -#endif // SHARE_SERVICE +#endif diff --git a/applets/kicker/plugin/runnermatchesmodel.cpp b/applets/kicker/plugin/runnermatchesmodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/runnermatchesmodel.cpp @@ -0,0 +1,257 @@ +/*************************************************************************** + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "runnermatchesmodel.h" +#include "runnermodel.h" +#include "actionlist.h" + +#include +#include + +#include +#include +#include + +#include + +RunnerMatchesModel::RunnerMatchesModel(const QString &runnerId, const QString &name, + Plasma::RunnerManager *manager, QObject *parent) +: AbstractModel(parent) +, m_runnerId(runnerId) +, m_name(name) +, m_runnerManager(manager) +{ +} + +QString RunnerMatchesModel::description() const +{ + return m_name; +} + +QVariant RunnerMatchesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_matches.count()) { + return QVariant(); + } + + Plasma::QueryMatch match = m_matches.at(index.row()); + + if (role == Qt::DisplayRole) { + return match.text(); + } else if (role == Qt::DecorationRole) { + if (!match.iconName().isEmpty()) { + return match.iconName(); + } + + return match.icon(); + } else if (role == Kicker::DescriptionRole) { + return match.subtext(); + } else if (role == Kicker::FavoriteIdRole) { + if (match.runner()->id() == QLatin1String("services")) { + return match.data().toString(); + } + } else if (role == Kicker::UrlRole) { + const QString &runnerId = match.runner()->id(); + if (runnerId == QLatin1String("baloosearch") || runnerId == QLatin1String("bookmarks")) { + return QUrl(match.data().toString()); + } else if (runnerId == QLatin1String("recentdocuments") + || runnerId == QLatin1String("services")) { + KService::Ptr service = KService::serviceByStorageId(match.data().toString()); + if (service) { + return QUrl::fromLocalFile(Kicker::resolvedServiceEntryPath(service)); + } + } + } else if (role == Kicker::HasActionListRole) { + // Hack to expose the protected Plasma::AbstractRunner::actions() method. + class MyRunner : public Plasma::AbstractRunner + { + public: + using Plasma::AbstractRunner::actions; + }; + + MyRunner *runner = static_cast(match.runner()); + + Q_ASSERT(runner); + + return match.runner()->id() == QLatin1String("services") || !runner->actions().isEmpty(); + } else if (role == Kicker::ActionListRole) { + QVariantList actionList; + + foreach (QAction *action, m_runnerManager->actionsForMatch(match)) { + QVariantMap item = Kicker::createActionItem(action->text(), action->icon().name(), QStringLiteral("runnerAction"), + QVariant::fromValue(action)); + + actionList << item; + } + + // Only try to get a KService for matches from the services runner. Assuming + // that any other runner returns something we want to turn into a KService is + // unsafe, e.g. files from the Baloo runner might match a storageId just by + // accident, creating a dangerous false positive. + if (match.runner()->id() != QLatin1String("services")) { + return actionList; + } + + const KService::Ptr service = KService::serviceByStorageId(match.data().toString()); + + if (service) { + if (!actionList.isEmpty()) { + actionList << Kicker::createSeparatorActionItem(); + } + + const QVariantList &jumpListActions = Kicker::jumpListActions(service); + if (!jumpListActions.isEmpty()) { + actionList << jumpListActions << Kicker::createSeparatorActionItem(); + } + + QObject *appletInterface = static_cast(parent())->appletInterface(); + + bool systemImmutable = false; + if (appletInterface) { + systemImmutable = (appletInterface->property("immutability").toInt() == Plasma::Types::SystemImmutable); + } + + const QVariantList &addLauncherActions = Kicker::createAddLauncherActionList(appletInterface, service); + if (!systemImmutable && !addLauncherActions.isEmpty()) { + actionList << addLauncherActions << Kicker::createSeparatorActionItem(); + } + + const QVariantList &recentDocuments = Kicker::recentDocumentActions(service); + if (!recentDocuments.isEmpty()) { + actionList << recentDocuments << Kicker::createSeparatorActionItem(); + } + + // Don't allow adding launchers, editing, hiding, or uninstalling applications + // when system is immutable. + if (systemImmutable) { + return actionList; + } + + if (service->isApplication()) { + actionList << Kicker::editApplicationAction(service); + actionList << Kicker::appstreamActions(service); + } + } + + return actionList; + } + + return QVariant(); +} + +int RunnerMatchesModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_matches.count(); +} + +bool RunnerMatchesModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (row < 0 || row >= m_matches.count()) { + return false; + } + + Plasma::QueryMatch match = m_matches.at(row); + + if (!match.isEnabled()) { + return false; + } + + QObject *appletInterface = static_cast(parent())->appletInterface(); + + const KService::Ptr service = KService::serviceByStorageId(match.data().toString()); + + if (Kicker::handleAddLauncherAction(actionId, appletInterface, service)) { + return true; + } else if (Kicker::handleEditApplicationAction(actionId, service)) { + return true; + } else if (Kicker::handleAppstreamActions(actionId, argument)) { + return true; + } else if (actionId == QLatin1String("_kicker_jumpListAction")) { + return KRun::run(argument.toString(), {}, nullptr, service ? service->name() : QString(), service ? service->icon() : QString()); + } else if (actionId == QLatin1String("_kicker_recentDocument") + || actionId == QLatin1String("_kicker_forgetRecentDocuments")) { + return Kicker::handleRecentDocumentAction(service, actionId, argument); + } + + if (!actionId.isEmpty()) { + QObject *obj = argument.value(); + + if (!obj) { + return false; + } + + QAction *action = qobject_cast(obj); + + if (!action) { + return false; + } + + match.setSelectedAction(action); + } + + m_runnerManager->run(match); + + return true; +} + +void RunnerMatchesModel::setMatches(const QList< Plasma::QueryMatch > &matches) +{ + int oldCount = m_matches.count(); + int newCount = matches.count(); + + bool emitCountChange = (oldCount != newCount); + + int ceiling = qMin(oldCount, newCount); + bool emitDataChange = false; + + for (int row = 0; row < ceiling; ++row) { + if (!(m_matches.at(row) == matches.at(row))) { + emitDataChange = true; + m_matches[row] = matches.at(row); + } + } + + if (emitDataChange) { + emit dataChanged(index(0, 0), index(ceiling - 1, 0)); + } + + if (newCount > oldCount) { + beginInsertRows(QModelIndex(), oldCount, newCount - 1); + + m_matches = matches; + + endInsertRows(); + } else if (newCount < oldCount) { + beginRemoveRows(QModelIndex(), newCount, oldCount - 1); + + m_matches = matches; + + endRemoveRows(); + } + + if (emitCountChange) { + emit countChanged(); + } +} + +AbstractModel *RunnerMatchesModel::favoritesModel() +{ + return static_cast(parent())->favoritesModel(); +} diff --git a/applets/kicker/plugin/runnermodel.h b/applets/kicker/plugin/runnermodel.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/runnermodel.h @@ -0,0 +1,108 @@ +/*************************************************************************** + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef RUNNERMODEL_H +#define RUNNERMODEL_H + +#include "abstractmodel.h" + +#include +#include + +#include + +namespace Plasma { + class RunnerManager; +} + +class AbstractModel; +class RunnerMatchesModel; + +class RunnerModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(AbstractModel* favoritesModel READ favoritesModel WRITE setFavoritesModel NOTIFY favoritesModelChanged) + Q_PROPERTY(QObject* appletInterface READ appletInterface WRITE setAppletInterface NOTIFY appletInterfaceChanged) + Q_PROPERTY(QStringList runners READ runners WRITE setRunners NOTIFY runnersChanged) + Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged) + Q_PROPERTY(bool mergeResults READ mergeResults WRITE setMergeResults NOTIFY mergeResultsChanged) + Q_PROPERTY(bool deleteWhenEmpty READ deleteWhenEmpty WRITE setDeleteWhenEmpty NOTIFY deleteWhenEmptyChanged) + + public: + explicit RunnerModel(QObject *parent = nullptr); + ~RunnerModel() override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int count() const; + + Q_INVOKABLE QObject *modelForRow(int row); + + QStringList runners() const; + void setRunners(const QStringList &runners); + + QString query() const; + void setQuery(const QString &query); + + AbstractModel *favoritesModel() const; + void setFavoritesModel(AbstractModel *model); + + QObject *appletInterface() const; + void setAppletInterface(QObject *appletInterface); + + bool mergeResults() const; + void setMergeResults(bool merge); + + bool deleteWhenEmpty() const; + void setDeleteWhenEmpty(bool deleteWhenEmpty); + + Q_SIGNALS: + void countChanged() const; + void favoritesModelChanged() const; + void appletInterfaceChanged() const; + void runnersChanged() const; + void queryChanged() const; + void mergeResultsChanged() const; + void deleteWhenEmptyChanged(); + + private Q_SLOTS: + void startQuery(); + void matchesChanged(const QList &matches); + + private: + void createManager(); + void clear(); + + AbstractModel *m_favoritesModel; + QObject *m_appletInterface; + Plasma::RunnerManager *m_runnerManager; + QStringList m_runners; + QList m_models; + QString m_query; + QTimer m_queryTimer; + bool m_mergeResults; + bool m_deleteWhenEmpty; +}; + +#endif diff --git a/applets/kicker/plugin/runnermodel.cpp b/applets/kicker/plugin/runnermodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/runnermodel.cpp @@ -0,0 +1,337 @@ +/*************************************************************************** + * Copyright (C) 2012 by Aurélien Gâteau * + * Copyright (C) 2014 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "runnermodel.h" +#include "runnermatchesmodel.h" + +#include + +#include +#include +#include + +RunnerModel::RunnerModel(QObject *parent) : QAbstractListModel(parent) +, m_favoritesModel(nullptr) +, m_appletInterface(nullptr) +, m_runnerManager(nullptr) +, m_mergeResults(false) +, m_deleteWhenEmpty(false) +{ + m_queryTimer.setSingleShot(true); + m_queryTimer.setInterval(10); + connect(&m_queryTimer, &QTimer::timeout, this, &RunnerModel::startQuery); +} + +RunnerModel::~RunnerModel() +{ +} + +QHash RunnerModel::roleNames() const +{ + return {{ Qt::DisplayRole, "display" }}; +} + +AbstractModel *RunnerModel::favoritesModel() const +{ + return m_favoritesModel; +} + +void RunnerModel::setFavoritesModel(AbstractModel *model) +{ + if (m_favoritesModel != model) { + m_favoritesModel = model; + + clear(); + + if (!m_query.isEmpty()) { + m_queryTimer.start(); + } + + emit favoritesModelChanged(); + } +} + +QObject *RunnerModel::appletInterface() const +{ + return m_appletInterface; +} + +void RunnerModel::setAppletInterface(QObject *appletInterface) +{ + if (m_appletInterface != appletInterface) { + m_appletInterface = appletInterface; + + clear(); + + if (!m_query.isEmpty()) { + m_queryTimer.start(); + } + + emit appletInterfaceChanged(); + } +} + +bool RunnerModel::deleteWhenEmpty() const +{ + return m_deleteWhenEmpty; +} + +void RunnerModel::setDeleteWhenEmpty(bool deleteWhenEmpty) +{ + if (m_deleteWhenEmpty != deleteWhenEmpty) { + m_deleteWhenEmpty = deleteWhenEmpty; + + clear(); + + if (!m_query.isEmpty()) { + m_queryTimer.start(); + } + + emit deleteWhenEmptyChanged(); + } +} + +bool RunnerModel::mergeResults() const +{ + return m_mergeResults; +} + +void RunnerModel::setMergeResults(bool merge) +{ + if (m_mergeResults != merge) { + m_mergeResults = merge; + + clear(); + + if (!m_query.isEmpty()) { + m_queryTimer.start(); + } + + emit mergeResultsChanged(); + } +} + +QVariant RunnerModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_models.count()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return m_models.at(index.row())->name(); + } + + return QVariant(); +} + +int RunnerModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_models.count(); +} + +int RunnerModel::count() const +{ + return rowCount(); +} + +QObject *RunnerModel::modelForRow(int row) +{ + if (row < 0 || row >= m_models.count()) { + return nullptr; + } + + return m_models.at(row); +} + +QStringList RunnerModel::runners() const +{ + return m_runners; +} + +void RunnerModel::setRunners(const QStringList &runners) +{ + if (m_runners.toSet() != runners.toSet()) { + m_runners = runners; + + if (m_runnerManager) { + m_runnerManager->setAllowedRunners(runners); + } + + emit runnersChanged(); + } +} + +QString RunnerModel::query() const +{ + return m_query; +} + +void RunnerModel::setQuery(const QString &query) +{ + if (m_query != query) { + m_query = query; + + m_queryTimer.start(); + + emit queryChanged(); + } +} + +void RunnerModel::startQuery() +{ + if (m_query.isEmpty()) { + clear(); + } + + if (m_query.isEmpty() && m_runnerManager) { + return; + } + + createManager(); + + m_runnerManager->launchQuery(m_query); +} + +void RunnerModel::matchesChanged(const QList &matches) +{ + // Group matches by runner. + // We do not use a QMultiHash here because it keeps values in LIFO order, while we want FIFO. + QHash > matchesForRunner; + + foreach (const Plasma::QueryMatch &match, matches) { + auto it = matchesForRunner.find(match.runner()->id()); + + if (it == matchesForRunner.end()) { + it = matchesForRunner.insert(match.runner()->id(), QList()); + } + + it.value().append(match); + } + + // Sort matches for all runners in descending order. This allows the best + // match to win whilest preserving order between runners. + for (auto &list : matchesForRunner) { + std::sort(list.begin(), list.end(), qGreater()); + } + + if (m_mergeResults) { + RunnerMatchesModel *matchesModel = nullptr; + + if (m_models.isEmpty()) { + matchesModel = new RunnerMatchesModel(QString(), i18n("Search results"), + m_runnerManager, this); + + beginInsertRows(QModelIndex(), 0, 0); + m_models.append(matchesModel); + endInsertRows(); + emit countChanged(); + } else { + matchesModel = m_models.at(0); + } + + QList matches; + + foreach (const QString &runnerId, m_runners) { + matches.append(matchesForRunner.take(runnerId)); + } + + matchesModel->setMatches(matches); + + return; + } + + // Assign matches to existing models. If there is no match for a model, delete it. + for (int row = m_models.count() - 1; row >= 0; --row) { + RunnerMatchesModel *matchesModel = m_models.at(row); + QList matches = matchesForRunner.take(matchesModel->runnerId()); + + if (m_deleteWhenEmpty && matches.isEmpty()) { + beginRemoveRows(QModelIndex(), row, row); + m_models.removeAt(row); + delete matchesModel; + endRemoveRows(); + emit countChanged(); + } else { + matchesModel->setMatches(matches); + } + } + + // At this point, matchesForRunner contains only matches for runners which + // do not have a model yet. Create new models for them. + if (!matchesForRunner.isEmpty()) { + auto it = matchesForRunner.constBegin(); + auto end = matchesForRunner.constEnd(); + int appendCount = 0; + + for (; it != end; ++it) { + QList matches = it.value(); + Q_ASSERT(!matches.isEmpty()); + RunnerMatchesModel *matchesModel = new RunnerMatchesModel(it.key(), + matches.first().runner()->name(), m_runnerManager, this); + matchesModel->setMatches(matches); + + if (it.key() == QLatin1String("services")) { + beginInsertRows(QModelIndex(), 0, 0); + m_models.prepend(matchesModel); + endInsertRows(); + emit countChanged(); + } else { + m_models.append(matchesModel); + ++appendCount; + } + } + + if (appendCount > 0) { + beginInsertRows(QModelIndex(), rowCount() - appendCount, rowCount() - 1); + endInsertRows(); + emit countChanged(); + } + } +} + +void RunnerModel::createManager() +{ + if (!m_runnerManager) { + m_runnerManager = new Plasma::RunnerManager(this); // FIXME: Which KConfigGroup is this using now? + m_runnerManager->setAllowedRunners(m_runners); + connect(m_runnerManager, &Plasma::RunnerManager::matchesChanged, + this, &RunnerModel::matchesChanged); + } +} + +void RunnerModel::clear() +{ + if (m_runnerManager) { + m_runnerManager->reset(); + } + + if (m_models.isEmpty()) { + return; + } + + beginResetModel(); + + qDeleteAll(m_models); + m_models.clear(); + + endResetModel(); + + emit countChanged(); +} diff --git a/applets/kicker/plugin/simplefavoritesmodel.h b/applets/kicker/plugin/simplefavoritesmodel.h new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/simplefavoritesmodel.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef SIMPLEFAVORITESMODEL_H +#define SIMPLEFAVORITESMODEL_H + +#include "abstractmodel.h" + +#include + +#include + +class SimpleFavoritesModel : public AbstractModel +{ + Q_OBJECT + + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(QStringList favorites READ favorites WRITE setFavorites NOTIFY favoritesChanged) + Q_PROPERTY(int maxFavorites READ maxFavorites WRITE setMaxFavorites NOTIFY maxFavoritesChanged) + Q_PROPERTY(int dropPlaceholderIndex READ dropPlaceholderIndex WRITE setDropPlaceholderIndex NOTIFY dropPlaceholderIndexChanged) + + public: + explicit SimpleFavoritesModel(QObject *parent = nullptr); + ~SimpleFavoritesModel() override; + + QString description() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + bool enabled() const; + void setEnabled(bool enable); + + QStringList favorites() const; + void setFavorites(const QStringList &favorites); + + int maxFavorites() const; + void setMaxFavorites(int max); + + Q_INVOKABLE bool isFavorite(const QString &id) const; + Q_INVOKABLE void addFavorite(const QString &id, int index = -1); + Q_INVOKABLE void removeFavorite(const QString &id); + + Q_INVOKABLE void moveRow(int from, int to); + + int dropPlaceholderIndex() const; + void setDropPlaceholderIndex(int index); + + AbstractModel* favoritesModel() override; + + public Q_SLOTS: + void refresh() override; + + Q_SIGNALS: + void enabledChanged() const; + void favoritesChanged() const; + void maxFavoritesChanged() const; + void dropPlaceholderIndexChanged(); + + private: + AbstractEntry *favoriteFromId(const QString &id); + + bool m_enabled; + + QList m_entryList; + QStringList m_favorites; + int m_maxFavorites; + + int m_dropPlaceholderIndex; +}; + +#endif diff --git a/applets/kicker/plugin/simplefavoritesmodel.cpp b/applets/kicker/plugin/simplefavoritesmodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/simplefavoritesmodel.cpp @@ -0,0 +1,333 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "simplefavoritesmodel.h" +#include "appentry.h" +#include "contactentry.h" +#include "fileentry.h" +#include "systementry.h" +#include "actionlist.h" + +#include + +SimpleFavoritesModel::SimpleFavoritesModel(QObject *parent) : AbstractModel(parent) +, m_enabled(true) +, m_maxFavorites(-1) +, m_dropPlaceholderIndex(-1) +{ +} + +SimpleFavoritesModel::~SimpleFavoritesModel() +{ + qDeleteAll(m_entryList); +} + +QString SimpleFavoritesModel::description() const +{ + return i18n("Favorites"); +} + +QVariant SimpleFavoritesModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || index.row() >= rowCount()) { + return QVariant(); + } + + if (index.row() == m_dropPlaceholderIndex) { + if (role == Kicker::IsDropPlaceholderRole) { + return true; + } else { + return QVariant(); + } + } + + int mappedIndex = index.row(); + + if (m_dropPlaceholderIndex != -1 && mappedIndex > m_dropPlaceholderIndex) { + --mappedIndex; + } + + const AbstractEntry *entry = m_entryList.at(mappedIndex); + + if (role == Qt::DisplayRole) { + return entry->name(); + } else if (role == Qt::DecorationRole) { + return entry->icon(); + } else if (role == Kicker::DescriptionRole) { + return entry->description(); + } else if (role == Kicker::FavoriteIdRole) { + return entry->id(); + } else if (role == Kicker::UrlRole) { + return entry->url(); + } else if (role == Kicker::HasActionListRole) { + return entry->hasActions(); + } else if (role == Kicker::ActionListRole) { + return entry->actions(); + } + + return QVariant(); +} + +int SimpleFavoritesModel::rowCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : m_entryList.count() + (m_dropPlaceholderIndex != -1 ? 1 : 0); +} + +bool SimpleFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (row < 0 || row >= m_entryList.count()) { + return false; + } + + return m_entryList.at(row)->run(actionId, argument); +} + +bool SimpleFavoritesModel::enabled() const +{ + return m_enabled; +} + +void SimpleFavoritesModel::setEnabled(bool enable) +{ + if (m_enabled != enable) { + m_enabled = enable; + + emit enabledChanged(); + } +} + +QStringList SimpleFavoritesModel::favorites() const +{ + return m_favorites; +} + +void SimpleFavoritesModel::setFavorites(const QStringList& favorites) +{ + QStringList _favorites(favorites); + _favorites.removeDuplicates(); + + if (_favorites != m_favorites) { + m_favorites = _favorites; + refresh(); + } +} + +int SimpleFavoritesModel::maxFavorites() const +{ + return m_maxFavorites; +} + +void SimpleFavoritesModel::setMaxFavorites(int max) +{ + if (m_maxFavorites != max) + { + m_maxFavorites = max; + + if (m_maxFavorites != -1 && m_favorites.count() > m_maxFavorites) { + refresh(); + } + + emit maxFavoritesChanged(); + } +} + +bool SimpleFavoritesModel::isFavorite(const QString &id) const +{ + return m_favorites.contains(id); +} + +void SimpleFavoritesModel::addFavorite(const QString &id, int index) +{ + if (!m_enabled || id.isEmpty()) { + return; + } + + if (m_maxFavorites != -1 && m_favorites.count() == m_maxFavorites) { + return; + } + + AbstractEntry *entry = favoriteFromId(id); + + if (!entry || !entry->isValid()) { + delete entry; + return; + } + + setDropPlaceholderIndex(-1); + + int insertIndex = (index != -1) ? index : m_entryList.count(); + + beginInsertRows(QModelIndex(), insertIndex, insertIndex); + + m_entryList.insert(insertIndex, entry); + m_favorites.insert(insertIndex, entry->id()); + + endInsertRows(); + + emit countChanged(); + emit favoritesChanged(); +} + +void SimpleFavoritesModel::removeFavorite(const QString &id) +{ + if (!m_enabled || id.isEmpty()) { + return; + } + + int index = m_favorites.indexOf(id); + + if (index != -1) { + setDropPlaceholderIndex(-1); + + beginRemoveRows(QModelIndex(), index, index); + + delete m_entryList[index]; + m_entryList.removeAt(index); + m_favorites.removeAt(index); + + endRemoveRows(); + + emit countChanged(); + emit favoritesChanged(); + } +} + +void SimpleFavoritesModel::moveRow(int from, int to) +{ + if (from >= m_favorites.count() || to >= m_favorites.count()) { + return; + } + + if (from == to) { + return; + } + + setDropPlaceholderIndex(-1); + + int modelTo = to + (to > from ? 1 : 0); + + bool ok = beginMoveRows(QModelIndex(), from, from, QModelIndex(), modelTo); + + if (ok) { + m_entryList.move(from, to); + m_favorites.move(from, to); + + endMoveRows(); + + emit favoritesChanged(); + } +} + +int SimpleFavoritesModel::dropPlaceholderIndex() const +{ + return m_dropPlaceholderIndex; +} + +void SimpleFavoritesModel::setDropPlaceholderIndex(int index) +{ + if (index == -1 && m_dropPlaceholderIndex != -1) { + beginRemoveRows(QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex); + + m_dropPlaceholderIndex = index; + + endRemoveRows(); + + emit countChanged(); + } else if (index != -1 && m_dropPlaceholderIndex == -1) { + beginInsertRows(QModelIndex(), index, index); + + m_dropPlaceholderIndex = index; + + endInsertRows(); + + emit countChanged(); + } else if (m_dropPlaceholderIndex != index) { + int modelTo = index + (index > m_dropPlaceholderIndex ? 1 : 0); + + bool ok = beginMoveRows(QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex, QModelIndex(), modelTo); + + if (ok) { + m_dropPlaceholderIndex = index; + + endMoveRows(); + } + } +} + +AbstractModel *SimpleFavoritesModel::favoritesModel() +{ + return this; +} + +void SimpleFavoritesModel::refresh() +{ + beginResetModel(); + + setDropPlaceholderIndex(-1); + + int oldCount = m_entryList.count(); + + qDeleteAll(m_entryList); + m_entryList.clear(); + + QStringList newFavorites; + + foreach(const QString &id, m_favorites) { + AbstractEntry *entry = favoriteFromId(id); + + if (entry && entry->isValid()) { + m_entryList << entry; + newFavorites << entry->id(); + + if (m_maxFavorites != -1 && newFavorites.count() == m_maxFavorites) { + break; + } + } else if (entry) { + delete entry; + } + } + + m_favorites = newFavorites; + + endResetModel(); + + if (oldCount != m_entryList.count()) { + emit countChanged(); + } + + emit favoritesChanged(); +} + +AbstractEntry *SimpleFavoritesModel::favoriteFromId(const QString &id) +{ + const QUrl url(id); + const QString &s = url.scheme(); + + if ((s.isEmpty() && id.contains(QLatin1String(".desktop"))) || s == QLatin1String("preferred")) { + return new AppEntry(this, id); + } else if (s == QLatin1String("ktp")) { + return new ContactEntry(this, id); + } else if (url.isValid() && !url.scheme().isEmpty()) { + return new FileEntry(this, url); + } else { + return new SystemEntry(this, id); + } + + return nullptr; +} diff --git a/dataengines/share/shareengine.h b/applets/kicker/plugin/submenu.h rename from dataengines/share/shareengine.h rename to applets/kicker/plugin/submenu.h --- a/dataengines/share/shareengine.h +++ b/applets/kicker/plugin/submenu.h @@ -1,5 +1,6 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2014 by David Edmundson * + * Copyright (C) 2014 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,28 +18,39 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_ENGINE_H -#define SHARE_ENGINE_H +#ifndef SUBMENU_H +#define SUBMENU_H -#include -#include +#include -class ShareService; -class ShareEngine : public Plasma::DataEngine +class SubMenu : public PlasmaQuick::Dialog { Q_OBJECT -public: - ShareEngine(QObject *parent, const QVariantList &args); - void init(); - Plasma::Service *serviceForSource(const QString &source) override; + Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged) + Q_PROPERTY(bool facingLeft READ facingLeft NOTIFY facingLeftChanged) -private Q_SLOTS: - void updatePlugins(const QStringList &changes); + public: + explicit SubMenu(QQuickItem *parent = nullptr); + ~SubMenu() override; -private: - friend class ShareService; + Q_INVOKABLE QRect availableScreenRectForItem(QQuickItem *item) const; + + QPoint popupPosition(QQuickItem *item, const QSize &size) override; + + int offset() const; + void setOffset(int offset); + + bool facingLeft() const { return m_facingLeft; } + + Q_SIGNALS: + void offsetChanged() const; + void facingLeftChanged() const; + + private: + int m_offset; + bool m_facingLeft; }; -#endif // SHARE_ENGINE +#endif diff --git a/applets/kicker/plugin/submenu.cpp b/applets/kicker/plugin/submenu.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/submenu.cpp @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2014 by David Edmundson * + * Copyright (C) 2014 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "submenu.h" + +#include + +#include + +#include + +SubMenu::SubMenu(QQuickItem *parent) : PlasmaQuick::Dialog(parent) +, m_offset(0) +, m_facingLeft(false) +{ + KWindowSystem::setType(winId(), NET::Menu); +} + +SubMenu::~SubMenu() +{ +} + +int SubMenu::offset() const +{ + return m_offset; +} + +void SubMenu::setOffset(int offset) +{ + if (m_offset != offset) { + m_offset = offset; + + emit offsetChanged(); + } +} + +QPoint SubMenu::popupPosition(QQuickItem* item, const QSize& size) +{ + if (!item || !item->window()) { + return QPoint(0, 0); + } + + QPointF pos = item->mapToScene(QPointF(0, 0)); + pos = item->window()->mapToGlobal(pos.toPoint()); + + pos.setX(pos.x() + m_offset + item->width()); + + QRect avail = availableScreenRectForItem(item); + + if (pos.x() + size.width() > avail.right()) { + pos.setX(pos.x() - m_offset - item->width() - size.width()); + + m_facingLeft = true; + emit facingLeftChanged(); + } + + pos.setY(pos.y() - margins()->property("top").toInt()); + + if (pos.y() + size.height() > avail.bottom()) { + int overshoot = std::ceil(((avail.bottom() - (pos.y() + size.height())) * -1) / item->height()) * item->height(); + + pos.setY(pos.y() - overshoot); + } + + return pos.toPoint(); +} + +QRect SubMenu::availableScreenRectForItem(QQuickItem *item) const +{ + QScreen *screen = QGuiApplication::primaryScreen(); + + const QPoint globalPosition = item->window()->mapToGlobal(item->position().toPoint()); + + foreach(QScreen *s, QGuiApplication::screens()) { + if (s->geometry().contains(globalPosition)) { + screen = s; + } + } + + return screen->availableGeometry(); +} diff --git a/dataengines/share/shareservice.h b/applets/kicker/plugin/systementry.h rename from dataengines/share/shareservice.h rename to applets/kicker/plugin/systementry.h --- a/dataengines/share/shareservice.h +++ b/applets/kicker/plugin/systementry.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,54 +17,50 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_SERVICE_H -#define SHARE_SERVICE_H +#ifndef SYSTEMENTRY_H +#define SYSTEMENTRY_H -#include "shareengine.h" +#include "abstractentry.h" -#include -#include -#include +class SystemEntry : public AbstractEntry +{ + public: + enum Action + { + NoAction = 0, + LockSession, + LogoutSession, + SaveSession, + SwitchUser, + SuspendToRam, + SuspendToDisk, + Reboot, + Shutdown + }; -class ShareProvider; + explicit SystemEntry(AbstractModel *owner, Action action); + explicit SystemEntry(AbstractModel *owner, const QString &id); -namespace Plasma { - class ServiceJob; -} + EntryType type() const override { return RunnableType; } -namespace KJSEmbed { - class Engine; -} + bool isValid() const override; -class ShareService : public Plasma::Service -{ - Q_OBJECT + QIcon icon() const override; + QString iconName() const; + QString name() const override; + QString group() const override; + QString description() const override; -public: - explicit ShareService(ShareEngine *engine); - Plasma::ServiceJob *createJob(const QString &operation, - QMap ¶meters) override; -}; + QString id() const override; -class ShareJob : public Plasma::ServiceJob -{ - Q_OBJECT + bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()) override; -public: - ShareJob(const QString &destination, const QString &operation, - QMap ¶meters, QObject *parent = nullptr); - ~ShareJob() override; - void start() override; + private: + void init(); -public Q_SLOTS: - void publish(); - void showResult(const QString &url); - void showError(const QString &msg); + Action m_action; + bool m_valid; -private: - QScopedPointer m_engine; - ShareProvider *m_provider; - KPackage::Package m_package; }; -#endif // SHARE_SERVICE +#endif diff --git a/applets/kicker/plugin/systementry.cpp b/applets/kicker/plugin/systementry.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/systementry.cpp @@ -0,0 +1,350 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "systementry.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "ksmserver_interface.h" +#include + +SystemEntry::SystemEntry(AbstractModel *owner, Action action) : AbstractEntry(owner) +, m_action(action) +, m_valid(false) +{ + init(); +} + +SystemEntry::SystemEntry(AbstractModel *owner, const QString &id) : AbstractEntry(owner) +, m_action(NoAction) +, m_valid(false) +{ + if (id == QLatin1String("lock-screen")) { + m_action = LockSession; + } else if (id == QLatin1String("logout")) { + m_action = LogoutSession; + } else if (id == QLatin1String("save-session")) { + m_action = SaveSession; + } else if (id == QLatin1String("switch-user")) { + m_action = SwitchUser; + } else if (id == QLatin1String("suspend")) { + m_action = SuspendToRam; + } else if (id == QLatin1String("hibernate")) { + m_action = SuspendToDisk; + } else if (id == QLatin1String("reboot")) { + m_action = Reboot; + } else if (id == QLatin1String("shutdown")) { + m_action = Shutdown; + } + + init(); +} + +void SystemEntry::init() +{ + switch (m_action) { + case NoAction: + m_valid = false; + break; + case LockSession: + m_valid = KAuthorized::authorizeAction(QStringLiteral("lock_screen")); + break; + case LogoutSession: + case SaveSession: + { + bool authorize = KAuthorized::authorizeAction(QStringLiteral("logout")) && KAuthorized::authorize(QStringLiteral("logout")); + + if (m_action == SaveSession) { + const KConfigGroup c(KSharedConfig::openConfig(QStringLiteral("ksmserverrc"), KConfig::NoGlobals), "General"); + + m_valid = authorize && c.readEntry("loginMode") == QLatin1String("restoreSavedSession"); + } else { + m_valid = authorize; + } + + break; + } + case SwitchUser: + m_valid = (KAuthorized::authorizeAction(QStringLiteral("start_new_session")) || KAuthorized::authorizeAction(QStringLiteral("switch_user"))) + && KDisplayManager().isSwitchable(); + break; + case SuspendToRam: + m_valid = Solid::PowerManagement::supportedSleepStates().contains(Solid::PowerManagement::SuspendState); + break; + case SuspendToDisk: + m_valid = Solid::PowerManagement::supportedSleepStates().contains(Solid::PowerManagement::HibernateState); + break; + case Reboot: + m_valid = KWorkSpace::canShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeReboot); + break; + case Shutdown: + m_valid = KWorkSpace::canShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeHalt); + break; + default: + m_valid = true; + } +} + +bool SystemEntry::isValid() const +{ + return m_valid; +} + +QIcon SystemEntry::icon() const +{ + const QString &name = iconName(); + + if (!name.isEmpty()) { + return QIcon::fromTheme(name, QIcon::fromTheme(QStringLiteral("unknown"))); + } + + return QIcon::fromTheme(QStringLiteral("unknown")); +} + +QString SystemEntry::iconName() const +{ + switch (m_action) { + case LockSession: + return QStringLiteral("system-lock-screen"); + break; + case LogoutSession: + return QStringLiteral("system-log-out"); + break; + case SaveSession: + return QStringLiteral("system-save-session"); + break; + case SwitchUser: + return QStringLiteral("system-switch-user"); + break; + case SuspendToRam: + return QStringLiteral("system-suspend"); + break; + case SuspendToDisk: + return QStringLiteral("system-suspend-hibernate"); + break; + case Reboot: + return QStringLiteral("system-reboot"); + break; + case Shutdown: + return QStringLiteral("system-shutdown"); + break; + default: + break; + } + + return QString(); +} + +QString SystemEntry::name() const +{ + switch (m_action) { + case LockSession: + return i18n("Lock"); + break; + case LogoutSession: + return i18n("Log Out"); + break; + case SaveSession: + return i18n("Save Session"); + break; + case SwitchUser: + return i18n("Switch User"); + break; + case SuspendToRam: + return i18nc("Suspend to RAM", "Sleep"); + break; + case SuspendToDisk: + return i18n("Hibernate"); + break; + case Reboot: + return i18n("Restart"); + break; + case Shutdown: + return i18n("Shut Down"); + break; + default: + break; + } + + return QString(); +} + +QString SystemEntry::group() const +{ + switch (m_action) { + case LockSession: + return i18n("Session"); + break; + case LogoutSession: + return i18n("Session"); + break; + case SaveSession: + return i18n("Session"); + break; + case SwitchUser: + return i18n("Session"); + break; + case SuspendToRam: + return i18n("System"); + break; + case SuspendToDisk: + return i18n("System"); + break; + case Reboot: + return i18n("System"); + break; + case Shutdown: + return i18n("System"); + break; + default: + break; + } + + return QString(); +} + +QString SystemEntry::description() const +{ + switch (m_action) { + case LockSession: + return i18n("Lock screen"); + break; + case LogoutSession: + return i18n("End session"); + break; + case SaveSession: + return i18n("Save Session"); + break; + case SwitchUser: + return i18n("Start a parallel session as a different user"); + break; + case SuspendToRam: + return i18n("Suspend to RAM"); + break; + case SuspendToDisk: + return i18n("Suspend to disk"); + break; + case Reboot: + return i18n("Restart computer"); + break; + case Shutdown: + return i18n("Turn off computer"); + break; + default: + break; + } + + return QString(); +} + +QString SystemEntry::id() const +{ + switch (m_action) { + case LockSession: + return QStringLiteral("lock-screen"); + break; + case LogoutSession: + return QStringLiteral("logout"); + break; + case SaveSession: + return QStringLiteral("save-session"); + break; + case SwitchUser: + return QStringLiteral("switch-user"); + break; + case SuspendToRam: + return QStringLiteral("suspend"); + break; + case SuspendToDisk: + return QStringLiteral("hibernate"); + break; + case Reboot: + return QStringLiteral("reboot"); + break; + case Shutdown: + return QStringLiteral("shutdown"); + break; + + default: + break; + } + + return QString(); +} + +bool SystemEntry::run(const QString& actionId, const QVariant &argument) +{ + Q_UNUSED(actionId) + Q_UNUSED(argument) + + switch (m_action) { + case LockSession: + { + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusInterface interface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), bus); + interface.asyncCall(QStringLiteral("Lock")); + break; + } + case LogoutSession: + KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeNone); + break; + case SaveSession: + { + org::kde::KSMServerInterface ksmserver(QStringLiteral("org.kde.ksmserver"), + QStringLiteral("/KSMServer"), QDBusConnection::sessionBus()); + + if (ksmserver.isValid()) { + ksmserver.saveCurrentSession(); + } + + break; + } + case SwitchUser: + { + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusInterface interface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface"), bus); + interface.asyncCall(QStringLiteral("openSwitchUserDialog")); + break; + }; + case SuspendToRam: + Solid::PowerManagement::requestSleep(Solid::PowerManagement::SuspendState, nullptr, nullptr); + break; + case SuspendToDisk: + Solid::PowerManagement::requestSleep(Solid::PowerManagement::HibernateState, nullptr, nullptr); + break; + case Reboot: + KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeReboot); + break; + case Shutdown: + KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeHalt); + break; + default: + return false; + } + + return true; +} diff --git a/dataengines/share/shareengine.h b/applets/kicker/plugin/systemmodel.h copy from dataengines/share/shareengine.h copy to applets/kicker/plugin/systemmodel.h --- a/dataengines/share/shareengine.h +++ b/applets/kicker/plugin/systemmodel.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2014-2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,28 +17,36 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_ENGINE_H -#define SHARE_ENGINE_H +#ifndef SYSTEMMODEL_H +#define SYSTEMMODEL_H -#include -#include +#include "abstractmodel.h" -class ShareService; +class SystemEntry; -class ShareEngine : public Plasma::DataEngine +class SystemModel : public AbstractModel { Q_OBJECT -public: - ShareEngine(QObject *parent, const QVariantList &args); - void init(); - Plasma::Service *serviceForSource(const QString &source) override; + public: + explicit SystemModel(QObject *parent = nullptr); + ~SystemModel() override; -private Q_SLOTS: - void updatePlugins(const QStringList &changes); + QString description() const override; -private: - friend class ShareService; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + + protected Q_SLOTS: + void refresh() override; + + private: + void init(); + + QList m_entryList; }; -#endif // SHARE_ENGINE +#endif diff --git a/applets/kicker/plugin/systemmodel.cpp b/applets/kicker/plugin/systemmodel.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/systemmodel.cpp @@ -0,0 +1,135 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "systemmodel.h" +#include "actionlist.h" +#include "simplefavoritesmodel.h" +#include "systementry.h" + +#include + +#include +#include + +SystemModel::SystemModel(QObject *parent) : AbstractModel(parent) +{ + init(); + + m_favoritesModel = new SimpleFavoritesModel(this); + + const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/ksmserverrc"); + + KDirWatch *watch = new KDirWatch(this); + + watch->addFile(configFile); + + connect(watch, &KDirWatch::dirty, this, &SystemModel::refresh); + connect(watch, &KDirWatch::created, this, &SystemModel::refresh); +} + +SystemModel::~SystemModel() +{ + qDeleteAll(m_entryList); +} + +void SystemModel::init() +{ + QList actions; + + actions << new SystemEntry(this, SystemEntry::LockSession); + actions << new SystemEntry(this, SystemEntry::LogoutSession); + actions << new SystemEntry(this, SystemEntry::SaveSession); + actions << new SystemEntry(this, SystemEntry::SwitchUser); + actions << new SystemEntry(this, SystemEntry::SuspendToRam); + actions << new SystemEntry(this, SystemEntry::SuspendToDisk); + actions << new SystemEntry(this, SystemEntry::Reboot); + actions << new SystemEntry(this, SystemEntry::Shutdown); + + foreach(SystemEntry *entry, actions) { + if (entry->isValid()) { + m_entryList << entry; + } else { + delete entry; + } + } +} + +QString SystemModel::description() const +{ + return i18n("System actions"); +} + +QVariant SystemModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_entryList.count()) { + return QVariant(); + } + + const SystemEntry *entry = m_entryList.at(index.row()); + + if (role == Qt::DisplayRole) { + return entry->name(); + } else if (role == Qt::DecorationRole) { + return entry->iconName(); + } else if (role == Kicker::DescriptionRole) { + return entry->description(); + } else if (role == Kicker::GroupRole) { + return entry->group(); + } else if (role == Kicker::FavoriteIdRole) { + return entry->id(); + } else if (role == Kicker::HasActionListRole) { + return entry->hasActions(); + } else if (role == Kicker::ActionListRole) { + return entry->actions(); + } + + return QVariant(); +} + +int SystemModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_entryList.count(); +} + +bool SystemModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (row >= 0 && row < m_entryList.count()) { + m_entryList.at(row)->run(actionId, argument); + + return true; + } + + return false; +} + +void SystemModel::refresh() +{ + beginResetModel(); + + qDeleteAll(m_entryList); + m_entryList.clear(); + + init(); + + endResetModel(); + + emit countChanged(); + + m_favoritesModel->refresh(); +} diff --git a/dataengines/share/packagestructure/share_package.h b/applets/kicker/plugin/systemsettings.h rename from dataengines/share/packagestructure/share_package.h rename to applets/kicker/plugin/systemsettings.h --- a/dataengines/share/packagestructure/share_package.h +++ b/applets/kicker/plugin/systemsettings.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright 2010 Artur Duque de Souza * + * Copyright (C) 2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,16 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_PACKAGE_H -#define SHARE_PACKAGE_H +#ifndef SYSTEMSETTINGS_H +#define SYSTEMSETTINGS_H -#include +#include -class SharePackage : public KPackage::PackageStructure +class SystemSettings : public QObject { -public: - explicit SharePackage(QObject *parent = nullptr, QVariantList args = QVariantList()); - void initPackage(KPackage::Package *package) override; + Q_OBJECT + + public: + explicit SystemSettings(QObject *parent = nullptr); + ~SystemSettings() override; + + Q_INVOKABLE QString picturesLocation() const; }; #endif diff --git a/dataengines/share/backends/kde/contents/code/main.js b/applets/kicker/plugin/systemsettings.cpp rename from dataengines/share/backends/kde/contents/code/main.js rename to applets/kicker/plugin/systemsettings.cpp --- a/dataengines/share/backends/kde/contents/code/main.js +++ b/applets/kicker/plugin/systemsettings.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010 by Artur Duque de Souza * + * Copyright (C) 2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,28 +17,30 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -function url() { - return "http://paste.kde.org/api/xml/create"; -} +#include "systemsettings.h" -function method() { - return "POST"; -} +#include -function contentKey() { - return "data"; +SystemSettings::SystemSettings(QObject *parent) : QObject(parent) +{ } -function setup() { - provider.addQueryItem("language", "text"); +SystemSettings::~SystemSettings() +{ } -function handleResultData(data) { - var res = provider.parseXML("id", data); - if (res == "") { - provider.error(data); - return; +QString SystemSettings::picturesLocation() const +{ + QString path; + + const QStringList &locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); + + if (!locations.isEmpty()) { + path = locations.at(0); + } else { + // HomeLocation is guaranteed not to be empty. + path = QStandardPaths::standardLocations(QStandardPaths::HomeLocation).at(0); } - provider.success("http://paste.kde.org/" + res); -} + return path; +} diff --git a/dataengines/share/shareengine.h b/applets/kicker/plugin/wheelinterceptor.h rename from dataengines/share/shareengine.h rename to applets/kicker/plugin/wheelinterceptor.h --- a/dataengines/share/shareengine.h +++ b/applets/kicker/plugin/wheelinterceptor.h @@ -1,5 +1,5 @@ -/*************************************************************************** - * Copyright 2010 Artur Duque de Souza * +/************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,28 +17,36 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef SHARE_ENGINE_H -#define SHARE_ENGINE_H +#ifndef WHEELINTERCEPTOR_H +#define WHEELINTERCEPTOR_H -#include -#include +#include +#include -class ShareService; - -class ShareEngine : public Plasma::DataEngine +class WheelInterceptor : public QQuickItem { Q_OBJECT -public: - ShareEngine(QObject *parent, const QVariantList &args); - void init(); - Plasma::Service *serviceForSource(const QString &source) override; + Q_PROPERTY(QQuickItem* destination READ destination WRITE setDestination NOTIFY destinationChanged) + + public: + explicit WheelInterceptor(QQuickItem *parent = nullptr); + ~WheelInterceptor() override; + + QQuickItem *destination() const; + void setDestination(QQuickItem *destination); + + Q_INVOKABLE QQuickItem *findWheelArea(QQuickItem *parent) const; + + Q_SIGNALS: + void destinationChanged() const; + void wheelMoved(QPoint delta) const; -private Q_SLOTS: - void updatePlugins(const QStringList &changes); + protected: + void wheelEvent(QWheelEvent *event) override; -private: - friend class ShareService; + private: + QPointer m_destination; }; -#endif // SHARE_ENGINE +#endif diff --git a/dataengines/share/backends/privatepaste/contents/code/main.js b/applets/kicker/plugin/wheelinterceptor.cpp rename from dataengines/share/backends/privatepaste/contents/code/main.js rename to applets/kicker/plugin/wheelinterceptor.cpp --- a/dataengines/share/backends/privatepaste/contents/code/main.js +++ b/applets/kicker/plugin/wheelinterceptor.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010 by Artur Duque de Souza * + * Copyright (C) 2014-2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,30 +17,56 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -function url() { - return "http://privatepaste.com/save"; +#include "wheelinterceptor.h" + +#include + +WheelInterceptor::WheelInterceptor(QQuickItem *parent) : QQuickItem(parent) +{ +} + +WheelInterceptor::~WheelInterceptor() +{ } -function contentKey() { - return "paste_content"; +QQuickItem* WheelInterceptor::destination() const +{ + return m_destination; } -function setup() { - provider.addQueryItem("_xsrf", "d38415c699a0408ebfce524c2dc0a4ba"); - provider.addQueryItem("formatting", "No Formatting"); - provider.addQueryItem("line_numbers", "on"); - provider.addQueryItem("expire", "31536000"); - provider.addQueryItem("secure_paste", "off"); +void WheelInterceptor::setDestination(QQuickItem *destination) +{ + if (m_destination != destination) { + m_destination = destination; + + emit destinationChanged(); + } } -function handleResultData(data) { - var res = data.match("(Error.+)"); - if (res != "") { - return; +void WheelInterceptor::wheelEvent(QWheelEvent* event) +{ + if (m_destination) { + QCoreApplication::sendEvent(m_destination, event); } - provider.error(data); + + emit wheelMoved(event->angleDelta()); } -function handleRedirection(url) { - provider.success(url); +QQuickItem *WheelInterceptor::findWheelArea(QQuickItem *parent) const +{ + if (!parent) { + return nullptr; + } + + foreach(QQuickItem *child, parent->childItems()) { + // HACK: ScrollView adds the WheelArea below its flickableItem with + // z==-1. This is reasonable non-risky considering we know about + // everything else in there, and worst case we break the mouse wheel. + if (child->z() == -1) { + return child; + } + } + + return nullptr; } + diff --git a/dataengines/share/backends/pasteopensuseorg/contents/code/main.js b/applets/kicker/plugin/windowsystem.h rename from dataengines/share/backends/pasteopensuseorg/contents/code/main.js rename to applets/kicker/plugin/windowsystem.h --- a/dataengines/share/backends/pasteopensuseorg/contents/code/main.js +++ b/applets/kicker/plugin/windowsystem.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010 by Will Stephenson * + * Copyright (C) 2014 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,30 +17,37 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -function url() { - return "http://paste.opensuse.org"; -} - -function contentKey() { - return "code"; -} - -function setup() { - provider.addQueryItem("name", "KDE"); - provider.addQueryItem("title", "mypaste"); - provider.addQueryItem("lang", "text"); - provider.addQueryItem("expire", "1440"); - provider.addQueryItem("submit", "submit"); -} - -function handleResultData(data) { - var res = data.match("(Info.+)"); - if (res != "") { - return; - } - provider.error(data); -} - -function handleRedirection(url) { - provider.success(url); -} +#ifndef WINDOWSYSTEM_H +#define WINDOWSYSTEM_H + +#include +#include +class QQuickItem; + +class WindowSystem : public QObject +{ + Q_OBJECT + + public: + explicit WindowSystem(QObject *parent = nullptr); + ~WindowSystem() override; + + bool eventFilter(QObject *watched, QEvent *event) override; + + Q_INVOKABLE void forceActive(QQuickItem *item); + + Q_INVOKABLE bool isActive(QQuickItem *item); + + Q_INVOKABLE void monitorWindowFocus(QQuickItem *item); + + Q_INVOKABLE void monitorWindowVisibility(QQuickItem *item); + + Q_SIGNALS: + void focusIn(QQuickWindow *window) const; + void hidden(QQuickWindow *window) const; + + private Q_SLOTS: + void monitoredWindowVisibilityChanged(QWindow::Visibility visibility) const; +}; + +#endif diff --git a/applets/kicker/plugin/windowsystem.cpp b/applets/kicker/plugin/windowsystem.cpp new file mode 100644 --- /dev/null +++ b/applets/kicker/plugin/windowsystem.cpp @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2014 by Eike Hein * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "windowsystem.h" + +#include +#include + +#include + +WindowSystem::WindowSystem(QObject *parent) : QObject(parent) +{ +} + +WindowSystem::~WindowSystem() +{ +} + +bool WindowSystem::eventFilter(QObject* watched, QEvent* event) +{ + if (event->type() == QEvent::FocusIn) { + removeEventFilter(watched); + emit focusIn(qobject_cast(watched)); + } + + return false; +} + +void WindowSystem::forceActive(QQuickItem *item) +{ + if (!item || !item->window()) { + return; + } + + KWindowSystem::forceActiveWindow(item->window()->winId()); + KWindowSystem::raiseWindow(item->window()->winId()); +} + +bool WindowSystem::isActive(QQuickItem *item) +{ + if (!item || !item->window()) { + return false; + } + + return item->window()->isActive(); +} + +void WindowSystem::monitorWindowFocus(QQuickItem* item) +{ + if (!item || !item->window()) { + return; + } + + item->window()->installEventFilter(this); +} + +void WindowSystem::monitorWindowVisibility(QQuickItem* item) +{ + if (!item || !item->window()) { + return; + } + + connect(item->window(), &QQuickWindow::visibilityChanged, this, + &WindowSystem::monitoredWindowVisibilityChanged, Qt::UniqueConnection); +} + +void WindowSystem::monitoredWindowVisibilityChanged(QWindow::Visibility visibility) const +{ + bool visible = (visibility != QWindow::Hidden); + QQuickWindow *w = static_cast(QObject::sender()); + + if (!visible) { + emit hidden(w); + } +} diff --git a/applets/lock_logout/contents/config/config.qml b/applets/lock_logout/contents/config/config.qml --- a/applets/lock_logout/contents/config/config.qml +++ b/applets/lock_logout/contents/config/config.qml @@ -24,7 +24,7 @@ ConfigModel { ConfigCategory { name: i18n("General") - icon: "system-shutdown" + icon: "preferences-desktop-plasma" source: "ConfigGeneral.qml" } } diff --git a/applets/lock_logout/metadata.desktop b/applets/lock_logout/metadata.desktop --- a/applets/lock_logout/metadata.desktop +++ b/applets/lock_logout/metadata.desktop @@ -18,7 +18,7 @@ Name[eo]=Ŝloso/adiaŭo Name[es]=Bloquear/Terminar Name[et]=Lukustamine/väljalogimine -Name[eu]=Blokeatu/Amaitu saioa +Name[eu]=Giltzatu/Saio-itxi Name[fi]=Lukitus/uloskirjautuminen Name[fr]=Verrouillage / Déconnexion Name[fy]=Beskoattelje/ôfmelde @@ -69,7 +69,7 @@ Name[sv]=Låsning eller utloggning Name[ta]=சாற்று/ வெளிவருக Name[te]=లాక్/లాగ్అవుట్ -Name[tg]=Қулф/Баромадан +Name[tg]=Қулф/Баромад Name[th]=ล็อคหน้าจอ/ออกจากระบบ Name[tr]=Kilitle/Çık Name[ug]=قۇلۇپلا/تىزىمدىن چىق @@ -97,7 +97,7 @@ Comment[eo]=Ŝlosi ekranon aŭ elsaluti Comment[es]=Bloquear el escritorio o salir Comment[et]=Ekraani lukustamine või väljalogimine -Comment[eu]=Blokeatu pantaila edo amaitu saioa +Comment[eu]=Giltzatu pantaila edo saioa itxi Comment[fi]=Lukitse näyttö tai kirjaudu ulos Comment[fr]=Verrouille l'écran ou se déconnecte Comment[fy]=Lit de brûker it skerm beskoattelje of de sesje slute @@ -145,7 +145,6 @@ Comment[sv]=Lås skärmen eller logga ut Comment[ta]=Lock the screen or log out Comment[te]=తెరను లాక్ చేయుము లేదా లాగ్ అవుట్ చేయుము -Comment[tg]=Заблокировать экран или завершить сеанс Comment[th]=ล็อคหน้าจอหรือออกจากระบบ Comment[tr]=Ekranı kilitleyin ya da çıkın Comment[ug]=ئېكراننى قۇلۇپلايدۇ ياكى تىزىمدىن چىقىدۇ @@ -156,6 +155,7 @@ Comment[zh_CN]=锁定屏幕或注销 Comment[zh_TW]=鎖定螢幕或登出 Keywords=Lock;Logout;Sleep;Hibernate;Switch User; +Keywords[ast]=Bloquiar;Bloquéu;Suspender;Suspensión;Dormir;Dormición;Ivernar;Ivernación;Hibernar;Hibernación;Cambiar d'usuariu;Cambéu d'usuariu;Cambéu d'usuarios Keywords[bs]=Zaključavanje;Odjava;Spavanje;Hibernacija;Promjena korisnika; Keywords[ca]=Bloqueig;Sortida;Adorm;Hiberna;Canvi d'usuari; Keywords[ca@valencia]=Bloqueig;Eixida;Adorm;Hiberna;Canvi d'usuari; @@ -165,7 +165,7 @@ Keywords[en_GB]=Lock;Logout;Sleep;Hibernate;Switch User; Keywords[es]=Bloquear;Terminar la sesión;Dormir;Hibernar;Cambiar de usuario; Keywords[et]=Lukustamine;Väljalogimine;Uni;Talveuni;Kasutaja vahetamine; -Keywords[eu]=Giltzatu;blokeatu;saio-bukaera;lokartu;hibernatu;aldatu erabiltzailea; +Keywords[eu]=giltzatu;blokeatu;saio-itxi;egin lo;hibernatu;aldatu erabiltzailea; Keywords[fi]=Lukitse;kirjaudu ulos;valmiustila;lepotila;vaihda käyttäjää; Keywords[fr]=Verrouillage;Déconnexion;Hibernation;Changement d'utilisateur; Keywords[gl]=Trancar;saír;durmir;hibernar;cambiar de usuario; @@ -178,7 +178,7 @@ Keywords[ja]=ロック;ログアウト;スリープ;ハイバネート;ユーザの切り替え; Keywords[kk]=Lock;Logout;Sleep;Hibernate;Switch User; Keywords[ko]=Lock;Logout;Sleep;Hibernate;Switch User;잠금;로그아웃;대기;대기 모드;최대 절전;최대 절전 모드;사용자 전환; -Keywords[lt]=Užrakinti;Atsijungti;Sustabdyti į RAM;Miegoti;Sustabdyti į diską;Hibernuoti;Perjungti naudotoją; +Keywords[lt]=Užrakinti;Uzrakinti;Atsijungti;Pristabdyti;Pristabdyti į RAM;Miegoti;Miegas;Miego veiksena;Miego režimas;Miego rezimas;Pristabdyti i RAM;Pristabdyti i diska;Pristabdyti į diską;Sustabdyti;Užmigdyti;Uzmigdyti;Hibernuoti;Perjungti naudotoją;Perjungti naudotoja Keywords[mr]=कुलूप; बाहेर पडा; झोप; हायबरनेट; वापरकर्ता बदला; Keywords[nb]=Lås; Logg ut; Hvile; Dvale; Bytt bruker; Keywords[nds]=Slott,afsluten,afmellen,slapen,infreren,Bruker wesseln;Brukerwessel @@ -189,7 +189,7 @@ Keywords[pt]=Bloquear;Encerrar;Suspender;Hibernar;Mudar de Utilizador; Keywords[pt_BR]=Bloquear;Encerrar;Suspender;Hibernar;Trocar usuário; Keywords[ro]=blochează;ieși;adormire;dormi;hibernare;schimbă utilizatorul;comutare utilizatori; -Keywords[ru]=Lock;Logout;Sleep;Hibernate;Switch User;блокировать;блокировка;выход;выход из системы;спящий режим;сон;режим гибернации;гибернация;сменить пользователя;смена пользователя; +Keywords[ru]=Lock;Logout;Sleep;Hibernate;Switch User;блокировать;блокировка;выход;выход из системы;ждущий режим;спящий режим;сон;режим гибернации;гибернация;сменить пользователя;смена пользователя; Keywords[sk]=Zamknúť;Odhlásiť;Uspať;Hibernovať;Prepnúť používateľa; Keywords[sl]=zaklep;odjava;pripravljenost;mirovanje;zamenjava uporabnika; Keywords[sr]=Lock;Logout;Sleep;Hibernate;Switch User;закључавање;одјављивање;спавање;хибернација;пребацивање; diff --git a/applets/mediacontroller/contents/ui/main.qml b/applets/mediacontroller/contents/ui/main.qml --- a/applets/mediacontroller/contents/ui/main.qml +++ b/applets/mediacontroller/contents/ui/main.qml @@ -49,7 +49,21 @@ var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) return decodeURIComponent(lastUrlPart) } - property string artist: currentMetadata ? currentMetadata["xesam:artist"] || "" : "" + property string artist: { + if (!currentMetadata) { + return "" + } + var xesamArtist = currentMetadata["xesam:artist"] + if (!xesamArtist) { + return ""; + } + + if (typeof xesamArtist == "string") { + return xesamArtist + } else { + return xesamArtist.join(", ") + } + } property string albumArt: currentMetadata ? currentMetadata["mpris:artUrl"] || "" : "" readonly property string identity: !root.noPlayer ? mpris2Source.currentData.Identity || mpris2Source.current : "" diff --git a/applets/mediacontroller/metadata.desktop b/applets/mediacontroller/metadata.desktop --- a/applets/mediacontroller/metadata.desktop +++ b/applets/mediacontroller/metadata.desktop @@ -11,18 +11,19 @@ Name[en_GB]=Media Player Name[es]=Reproductor multimedia Name[et]=Meediamängija -Name[eu]=Multimedia-jotzailea +Name[eu]=Euskarri jotzailea Name[fi]=Mediasoitin Name[fr]=Lecteur multimédia Name[gl]=Reprodutor multimedia Name[he]=נגן מדיה Name[hu]=Médialejátszó Name[ia]=Media Player (Reproductor de Media) +Name[id]=Pemutar Media Name[is]=Margmiðlunarspilari Name[it]=Lettore multimediale Name[ja]=メディアプレーヤー Name[ko]=미디어 재생기 -Name[lt]=Kūrinių leistuvė +Name[lt]=Medijos leistuvė Name[nb]=Mediespiller Name[nds]=Medienafspeler Name[nl]=Mediaspeler @@ -39,6 +40,7 @@ Name[sr@ijekavianlatin]=medija plejer Name[sr@latin]=medija plejer Name[sv]=Mediaspelare +Name[tg]=Плеери медиа Name[tr]=Ortam Yürütücüsü Name[uk]=Програвач Name[x-test]=xxMedia Playerxx @@ -56,19 +58,19 @@ Comment[en_GB]=Media Player Controls Comment[es]=Controles del reproductor multimedia Comment[et]=Meediamängija juhtelemendid -Comment[eu]=Multimedia-jotzailearen kontrolak +Comment[eu]=Euskarri jotzaile kontrolak Comment[fi]=Mediasoittimen säätimet Comment[fr]=Contrôles du lecteur multimédia Comment[gl]=Controis do reprodutor multimedia Comment[he]=פקדי ניגון מדיה Comment[hu]=Médialejátszó vezérlők Comment[ia]=Controlos de Reproductor de Multimedia -Comment[id]=Kendali Player Media +Comment[id]=Kendali Pemutar Media Comment[is]=Stjórntæki margmiðlunarspilara Comment[it]=Controlli del lettore multimediale Comment[ja]=メディアプレーヤーコントロール Comment[ko]=미디어 재생기 제어 -Comment[lt]=Kūrinių leistuvės valdikliai +Comment[lt]=Medijos leistuvės valdikliai Comment[nb]=Styring for mediespiller Comment[nds]=Medienafspeler stüern Comment[nl]=Besturing van mediaspeler diff --git a/applets/notifications/CMakeLists.txt b/applets/notifications/CMakeLists.txt --- a/applets/notifications/CMakeLists.txt +++ b/applets/notifications/CMakeLists.txt @@ -3,6 +3,7 @@ set(notificationapplet_SRCS notificationapplet.cpp filemenu.cpp + globalshortcuts.cpp thumbnailer.cpp ) @@ -15,6 +16,8 @@ Qt5::Quick # for QQmlParserStatus KF5::I18n KF5::Plasma + KF5::PlasmaQuick + KF5::GlobalAccel KF5::KIOWidgets # for PreviewJob ) diff --git a/applets/notifications/globalshortcuts.h b/applets/notifications/globalshortcuts.h new file mode 100644 --- /dev/null +++ b/applets/notifications/globalshortcuts.h @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Kai Uwe Broulik + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License or any later version accepted by the membership of + * KDE e.V. (or its successor approved by the membership of KDE + * e.V.), which shall act as a proxy defined in Section 14 of + * version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +class QAction; + +class GlobalShortcuts : public QObject +{ + Q_OBJECT + +public: + explicit GlobalShortcuts(QObject *parent = nullptr); + ~GlobalShortcuts() override; + + Q_INVOKABLE void showDoNotDisturbOsd(bool doNotDisturb) const; + +signals: + void toggleDoNotDisturbTriggered(); + +private: + QAction *m_toggleDoNotDisturbAction; + +}; diff --git a/applets/notifications/globalshortcuts.cpp b/applets/notifications/globalshortcuts.cpp new file mode 100644 --- /dev/null +++ b/applets/notifications/globalshortcuts.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Kai Uwe Broulik + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License or any later version accepted by the membership of + * KDE e.V. (or its successor approved by the membership of KDE + * e.V.), which shall act as a proxy defined in Section 14 of + * version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "globalshortcuts.h" + +#include +#include +#include + +#include + +#include + +GlobalShortcuts::GlobalShortcuts(QObject *parent) + : QObject(parent) + , m_toggleDoNotDisturbAction(new QAction(this)) +{ + m_toggleDoNotDisturbAction->setObjectName(QStringLiteral("toggle do not disturb")); + m_toggleDoNotDisturbAction->setProperty("componentName", QStringLiteral("plasmashell")); + m_toggleDoNotDisturbAction->setText(i18n("Toggle do not disturb")); + m_toggleDoNotDisturbAction->setIcon(QIcon::fromTheme(QStringLiteral("notifications-disabled"))); + m_toggleDoNotDisturbAction->setShortcutContext(Qt::ApplicationShortcut); + connect(m_toggleDoNotDisturbAction, &QAction::triggered, this, &GlobalShortcuts::toggleDoNotDisturbTriggered); + + KGlobalAccel::self()->setGlobalShortcut(m_toggleDoNotDisturbAction, QKeySequence()); +} + +GlobalShortcuts::~GlobalShortcuts() = default; + +void GlobalShortcuts::showDoNotDisturbOsd(bool doNotDisturb) const +{ + QDBusMessage msg = QDBusMessage::createMethodCall( + QStringLiteral("org.kde.plasmashell"), + QStringLiteral("/org/kde/osdService"), + QStringLiteral("org.kde.osdService"), + QStringLiteral("showText") + ); + + const QString iconName = doNotDisturb ? QStringLiteral("notifications-disabled") : QStringLiteral("notifications"); + const QString text = doNotDisturb ? i18nc("OSD popup, keep short", "Notifications Off") + : i18nc("OSD popup, keep short", "Notifications On"); + + msg.setArguments({iconName, text}); + + QDBusConnection::sessionBus().call(msg, QDBus::NoBlock); +} diff --git a/applets/notifications/notificationapplet.h b/applets/notifications/notificationapplet.h --- a/applets/notifications/notificationapplet.h +++ b/applets/notifications/notificationapplet.h @@ -26,12 +26,16 @@ class QQuickItem; class QString; class QRect; +class QWindow; class NotificationApplet : public Plasma::Applet { Q_OBJECT Q_PROPERTY(bool dragActive READ dragActive NOTIFY dragActiveChanged) + Q_PROPERTY(int dragPixmapSize READ dragPixmapSize WRITE setDragPixmapSize NOTIFY dragPixmapSizeChanged) + + Q_PROPERTY(QWindow *focussedPlasmaDialog READ focussedPlasmaDialog NOTIFY focussedPlasmaDialogChanged) public: explicit NotificationApplet(QObject *parent, const QVariantList &data); @@ -41,20 +45,33 @@ void configChanged() override; bool dragActive() const; + + int dragPixmapSize() const; + void setDragPixmapSize(int dragPixmapSize); + Q_INVOKABLE bool isDrag(int oldX, int oldY, int newX, int newY) const; + Q_INVOKABLE void startDrag(QQuickItem *item, const QUrl &url, const QString &iconName); Q_INVOKABLE void startDrag(QQuickItem *item, const QUrl &url, const QPixmap &pixmap); + QWindow *focussedPlasmaDialog() const; + Q_INVOKABLE void setSelectionClipboardText(const QString &text); Q_INVOKABLE bool isPrimaryScreen(const QRect &rect) const; + Q_INVOKABLE QString iconNameForUrl(const QUrl &url) const; + Q_INVOKABLE void forceActivateWindow(QWindow *window); + signals: void dragActiveChanged(); + void dragPixmapSizeChanged(); + void focussedPlasmaDialogChanged(); private slots: void doDrag(QQuickItem *item, const QUrl &url, const QPixmap &pixmap); private: bool m_dragActive = false; + int m_dragPixmapSize = 48; // Bound to units.iconSizes.large in main.qml }; diff --git a/applets/notifications/notificationapplet.cpp b/applets/notifications/notificationapplet.cpp --- a/applets/notifications/notificationapplet.cpp +++ b/applets/notifications/notificationapplet.cpp @@ -23,13 +23,22 @@ #include #include +#include #include +#include +#include #include #include #include #include +#include + +#include + +#include #include "filemenu.h" +#include "globalshortcuts.h" #include "thumbnailer.h" NotificationApplet::NotificationApplet(QObject *parent, const QVariantList &data) @@ -39,10 +48,13 @@ if (!s_typesRegistered) { const char uri[] = "org.kde.plasma.private.notifications"; qmlRegisterType(uri, 2, 0, "FileMenu"); + qmlRegisterType(uri, 2, 0, "GlobalShortcuts"); qmlRegisterType(uri, 2, 0, "Thumbnailer"); qmlProtectModule(uri, 2); s_typesRegistered = true; } + + connect(qApp, &QGuiApplication::focusWindowChanged, this, &NotificationApplet::focussedPlasmaDialogChanged); } NotificationApplet::~NotificationApplet() = default; @@ -62,11 +74,29 @@ return m_dragActive; } +int NotificationApplet::dragPixmapSize() const +{ + return m_dragPixmapSize; +} + +void NotificationApplet::setDragPixmapSize(int dragPixmapSize) +{ + if (m_dragPixmapSize != dragPixmapSize) { + m_dragPixmapSize = dragPixmapSize; + emit dragPixmapSizeChanged(); + } +} + bool NotificationApplet::isDrag(int oldX, int oldY, int newX, int newY) const { return ((QPoint(oldX, oldY) - QPoint(newX, newY)).manhattanLength() >= qApp->styleHints()->startDragDistance()); } +void NotificationApplet::startDrag(QQuickItem *item, const QUrl &url, const QString &iconName) +{ + startDrag(item, url, QIcon::fromTheme(iconName).pixmap(m_dragPixmapSize, m_dragPixmapSize)); +} + void NotificationApplet::startDrag(QQuickItem *item, const QUrl &url, const QPixmap &pixmap) { // This allows the caller to return, making sure we don't crash if @@ -105,6 +135,11 @@ emit dragActiveChanged(); } +QWindow *NotificationApplet::focussedPlasmaDialog() const +{ + return qobject_cast(qApp->focusWindow()); +} + void NotificationApplet::setSelectionClipboardText(const QString &text) { // FIXME KDeclarative Clipboard item uses QClipboard::Mode for "mode" @@ -123,6 +158,23 @@ return rect == screen->geometry(); } +QString NotificationApplet::iconNameForUrl(const QUrl &url) const +{ + QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); + if (mime.isDefault()) { + return QString(); + } + + return mime.iconName(); +} + +void NotificationApplet::forceActivateWindow(QWindow *window) +{ + if (window && window->winId()) { + KWindowSystem::forceActiveWindow(window->winId()); + } +} + K_EXPORT_PLASMA_APPLET_WITH_JSON(icon, NotificationApplet, "metadata.json") #include "notificationapplet.moc" diff --git a/applets/notifications/package/contents/ui/CompactRepresentation.qml b/applets/notifications/package/contents/ui/CompactRepresentation.qml --- a/applets/notifications/package/contents/ui/CompactRepresentation.qml +++ b/applets/notifications/package/contents/ui/CompactRepresentation.qml @@ -24,6 +24,8 @@ import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.quickcharts 1.0 as Charts + MouseArea { id: compactRoot @@ -64,37 +66,27 @@ svg: notificationSvg visible: opacity > 0 - elementId: "notification-disabled" + elementId: "notification-inactive" - Item { - id: jobProgressItem - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - } - width: notificationIcon.width * (jobsPercentage / 100) + Charts.PieChart { + id: chart + + anchors.fill: parent - clip: true visible: false - PlasmaCore.SvgItem { - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - } - width: notificationIcon.width + range { from: 0; to: 100; automatic: false } - svg: notificationSvg - elementId: "notification-progress-active" - } + valueSources: Charts.SingleValueSource { value: compactRoot.jobsPercentage } + colorSource: Charts.SingleValueSource { value: theme.highlightColor } + + thickness: units.devicePixelRatio * 5 } PlasmaComponents.Label { id: countLabel anchors.centerIn: parent - width: Math.round(Math.min(parent.width, parent.height) * 0.75) + width: Math.round(Math.min(parent.width, parent.height) * (text.length > 1 ? 0.67 : 0.75)) height: width fontSizeMode: Text.Fit font.pointSize: 1024 @@ -104,6 +96,7 @@ verticalAlignment: Text.AlignVCenter text: compactRoot.unreadCount || "" renderType: Text.QtRendering + visible: false } PlasmaComponents.BusyIndicator { @@ -133,15 +126,20 @@ PropertyChanges { target: countLabel text: compactRoot.jobsCount + visible: true } PropertyChanges { target: busyIndicator - visible: true + visible: compactRoot.jobsPercentage == 0 } PropertyChanges { target: jobProgressItem visible: true } + PropertyChanges { + target: chart + visible: true + } }, State { // do not disturb when: compactRoot.inhibited @@ -157,14 +155,11 @@ } }, State { // unread notifications + name: "UNREAD" when: compactRoot.unreadCount > 0 PropertyChanges { target: notificationIcon - elementId: "notification-empty" - } - PropertyChanges { - target: countLabel - text: compactRoot.unreadCount + elementId: "notification-active" } } ] @@ -178,6 +173,30 @@ duration: units.longDuration easing.type: Easing.InOutQuad } + }, + Transition { + from: "" + to: "UNREAD" + SequentialAnimation { + RotationAnimation { + target: notificationIcon + to: 30 + easing.type: Easing.InOutQuad + duration: units.longDuration + } + RotationAnimation { + target: notificationIcon + to: -30 + easing.type: Easing.InOutQuad + duration: units.longDuration * 2 // twice the swing distance, keep speed uniform + } + RotationAnimation { + target: notificationIcon + to: 0 + easing.type: Easing.InOutQuad + duration: units.longDuration + } + } } ] diff --git a/applets/notifications/package/contents/ui/DraggableDelegate.qml b/applets/notifications/package/contents/ui/DraggableDelegate.qml new file mode 100644 --- /dev/null +++ b/applets/notifications/package/contents/ui/DraggableDelegate.qml @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Marco Martin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import QtQuick 2.10 +import org.kde.kirigami 2.11 as Kirigami + +MouseArea { + id: delegate + + property Item contentItem + property bool draggable: false + signal dismissRequested + + implicitWidth: contentItem ? contentItem.implicitWidth : 0 + implicitHeight: contentItem ? contentItem.implicitHeight : 0 + opacity: 1 - Math.min(1, 1.5 * Math.abs(x) / width) + + drag { + axis: Drag.XAxis + target: draggable && Kirigami.Settings.tabletMode ? this : null + } + + onReleased: { + if (Math.abs(x) > width / 2) { + delegate.dismissRequested(); + } else { + slideAnim.restart(); + } + } + + NumberAnimation { + id: slideAnim + target: delegate + property:"x" + to: 0 + duration: units.longDuration + } +} diff --git a/applets/notifications/package/contents/ui/DraggableFileArea.qml b/applets/notifications/package/contents/ui/DraggableFileArea.qml new file mode 100644 --- /dev/null +++ b/applets/notifications/package/contents/ui/DraggableFileArea.qml @@ -0,0 +1,71 @@ +/* + * Copyright 2016,2019 Kai Uwe Broulik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.8 + +MouseArea { + id: area + + signal activated + signal contextMenuRequested(int x, int y) + + property Item dragParent + property url dragUrl + property var dragPixmap + + readonly property bool dragging: plasmoid.nativeInterface.dragActive + + property int _pressX: -1 + property int _pressY: -1 + + preventStealing: true + cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onClicked: { + if (mouse.button === Qt.LeftButton) { + area.activated(); + } + } + onPressed: { + if (mouse.button === Qt.LeftButton) { + _pressX = mouse.x; + _pressY = mouse.y; + } else if (mouse.button === Qt.RightButton) { + area.contextMenuRequested(mouse.x, mouse.y); + } + } + onPositionChanged: { + if (_pressX !== -1 && _pressY !== -1 && plasmoid.nativeInterface.isDrag(_pressX, _pressY, mouse.x, mouse.y)) { + plasmoid.nativeInterface.startDrag(area.dragParent, area.dragUrl, area.dragPixmap); + _pressX = -1; + _pressY = -1; + } + } + onReleased: { + _pressX = -1; + _pressY = -1; + } + onContainsMouseChanged: { + if (!containsMouse) { + _pressX = -1; + _pressY = -1; + } + } +} diff --git a/applets/notifications/package/contents/ui/FullRepresentation.qml b/applets/notifications/package/contents/ui/FullRepresentation.qml --- a/applets/notifications/package/contents/ui/FullRepresentation.qml +++ b/applets/notifications/package/contents/ui/FullRepresentation.qml @@ -18,14 +18,15 @@ * along with this program. If not, see */ -import QtQuick 2.8 +import QtQuick 2.10 import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.components 3.0 as PlasmaComponents3 import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.kirigami 2.11 as Kirigami import org.kde.kcoreaddons 1.0 as KCoreAddons @@ -66,6 +67,8 @@ // header ColumnLayout { + id: header + visible: !Kirigami.Settings.isMobile Layout.fillWidth: true spacing: 0 @@ -76,6 +79,7 @@ RowLayout { id: dndRow spacing: units.smallSpacing + enabled: NotificationManager.Server.valid PlasmaComponents3.CheckBox { id: dndCheck @@ -96,10 +100,7 @@ // but disable only on click onClicked: { if (Globals.inhibited) { - notificationSettings.notificationsInhibitedUntil = undefined; - notificationSettings.revokeApplicationInhibitions(); - - notificationSettings.save(); + Globals.revokeInhibitions(); } } @@ -168,7 +169,7 @@ if (dndMenu.date.getDay() >= 5) { d = dndMenu.date; d.setHours(dndMorningHour); - // wraps around if neccessary + // wraps around if necessary d.setDate(d.getDate() + (7 - d.getDay() + 1)); d.setMinutes(0); d.setSeconds(0); @@ -222,6 +223,8 @@ var inhibitedUntil = notificationSettings.notificationsInhibitedUntil; var inhibitedByApp = notificationSettings.notificationsInhibitedByApplication; + var inhibitedByMirroredScreens = notificationSettings.inhibitNotificationsWhenScreensMirrored + && notificationSettings.screensMirrored; var sections = []; @@ -247,13 +250,18 @@ } } + if (inhibitedByMirroredScreens) { + sections.push(i18nc("Do not disturb because external mirrored screens connected", "Screens are mirrored")) + } + return sections.join(" · "); } visible: text !== "" } } PlasmaCore.SvgItem { + visible: header.visible elementId: "horizontal-line" Layout.fillWidth: true // why is this needed here but not in the delegate? @@ -350,9 +358,10 @@ add: Transition { SequentialAnimation { + PropertyAction { property: "opacity"; value: 0 } PauseAnimation { duration: units.longDuration } ParallelAnimation { - NumberAnimation { property: "opacity"; from: 0; duration: units.longDuration } + NumberAnimation { property: "opacity"; from: 0; to: 1; duration: units.longDuration } NumberAnimation { property: "height"; from: 0; duration: units.longDuration } } } @@ -362,9 +371,15 @@ } remove: Transition { + id: removeTransition ParallelAnimation { NumberAnimation { property: "opacity"; to: 0; duration: units.longDuration } - NumberAnimation { property: "x"; to: list.width; duration: units.longDuration } + NumberAnimation { + id: removeXAnimation + property: "x" + to: list.width + duration: units.longDuration + } } } removeDisplaced: Transition { @@ -380,180 +395,234 @@ criteria: ViewSection.FullString } - delegate: Loader { - id: delegateLoader + delegate: DraggableDelegate { + id: delegate width: list.width - sourceComponent: model.isGroup ? groupDelegate : notificationDelegate + contentItem: delegateLoader - Component { - id: groupDelegate - NotificationHeader { - applicationName: model.applicationName - applicationIconSource: model.applicationIconName + draggable: !model.isGroup && model.type != NotificationManager.Notifications.JobType - // don't show timestamp for group + onDismissRequested: { + // Setting the animation target explicitly before removing the notification: + // Using ViewTransition.item.x to get the x position in the animation + // causes random crash in attached property access (cf. Bug 414066) + if (x < 0) { + removeXAnimation.to = -list.width; + } - configurable: model.configurable - closable: model.closable - closeButtonTooltip: i18n("Close Group") + historyModel.close(historyModel.index(index, 0)); + } - onCloseClicked: { - historyModel.close(historyModel.index(index, 0)) - if (list.count === 0) { - plasmoid.expanded = false; - } - } + Loader { + id: delegateLoader + width: list.width + sourceComponent: model.isGroup ? groupDelegate : notificationDelegate - onConfigureClicked: historyModel.configure(historyModel.index(index, 0)) - } - } + Component { + id: groupDelegate + NotificationHeader { + applicationName: model.applicationName + applicationIconSource: model.applicationIconName + originName: model.originName || "" - Component { - id: notificationDelegate - ColumnLayout { - spacing: units.smallSpacing - - RowLayout { - Item { - id: groupLineContainer - Layout.fillHeight: true - Layout.topMargin: units.smallSpacing - width: units.iconSizes.small - visible: model.isInGroup - - PlasmaCore.SvgItem { - elementId: "vertical-line" - svg: lineSvg - anchors.horizontalCenter: parent.horizontalCenter - width: units.iconSizes.small - height: parent.height + // don't show timestamp for group + + configurable: model.configurable + closable: model.closable + closeButtonTooltip: i18n("Close Group") + + onCloseClicked: { + historyModel.close(historyModel.index(index, 0)) + if (list.count === 0) { + root.closePassivePlasmoid(); } } - NotificationItem { - Layout.fillWidth: true + onConfigureClicked: historyModel.configure(historyModel.index(index, 0)) + } + } - notificationType: model.type - - inGroup: model.isInGroup - - applicationName: model.applicationName - applicationIconSource: model.applicationIconName - originName: model.originName || "" - - time: model.updated || model.created - - // configure button on every single notifications is bit overwhelming - configurable: !inGroup && model.configurable - - dismissable: model.type === NotificationManager.Notifications.JobType - && model.jobState !== NotificationManager.Notifications.JobStateStopped - && model.dismissed - // TODO would be nice to be able to undismiss jobs even when they autohide - && notificationSettings.permanentJobPopups - dismissed: model.dismissed || false - closable: model.closable - - summary: model.summary - body: model.body || "" - icon: model.image || model.iconName - - urls: model.urls || [] - - jobState: model.jobState || 0 - percentage: model.percentage || 0 - jobError: model.jobError || 0 - suspendable: !!model.suspendable - killable: !!model.killable - jobDetails: model.jobDetails || null - - configureActionLabel: model.configureActionLabel || "" - // In the popup the default action is triggered by clicking on the popup - // however in the list this is undesirable, so instead show a clickable button - // in case you have a non-expired notification in history (do not disturb mode) - // unless it has the same label as an action - readonly property bool addDefaultAction: (model.hasDefaultAction - && model.defaultActionLabel - && (model.actionLabels || []).indexOf(model.defaultActionLabel) === -1) ? true : false - actionNames: { - var actions = (model.actionNames || []); - if (addDefaultAction) { - actions.unshift("default"); // prepend - } - return actions; - } - actionLabels: { - var labels = (model.actionLabels || []); - if (addDefaultAction) { - labels.unshift(model.defaultActionLabel); + Component { + id: notificationDelegate + ColumnLayout { + spacing: units.smallSpacing + + RowLayout { + Item { + id: groupLineContainer + Layout.fillHeight: true + Layout.topMargin: units.smallSpacing + width: units.iconSizes.small + visible: model.isInGroup + + PlasmaCore.SvgItem { + elementId: "vertical-line" + svg: lineSvg + anchors.horizontalCenter: parent.horizontalCenter + width: units.iconSizes.small + height: parent.height } - return labels; } - onCloseClicked: { - historyModel.close(historyModel.index(index, 0)); - if (list.count === 0) { - plasmoid.expanded = false; + NotificationItem { + Layout.fillWidth: true + + notificationType: model.type + + inGroup: model.isInGroup + + applicationName: model.applicationName + applicationIconSource: model.applicationIconName + originName: model.originName || "" + + time: model.updated || model.created + + // configure button on every single notifications is bit overwhelming + configurable: !inGroup && model.configurable + + dismissable: model.type === NotificationManager.Notifications.JobType + && model.jobState !== NotificationManager.Notifications.JobStateStopped + && model.dismissed + // TODO would be nice to be able to undismiss jobs even when they autohide + && notificationSettings.permanentJobPopups + dismissed: model.dismissed || false + closable: model.closable + + summary: model.summary + body: model.body || "" + icon: model.image || model.iconName + + urls: model.urls || [] + + jobState: model.jobState || 0 + percentage: model.percentage || 0 + jobError: model.jobError || 0 + suspendable: !!model.suspendable + killable: !!model.killable + jobDetails: model.jobDetails || null + + configureActionLabel: model.configureActionLabel || "" + // In the popup the default action is triggered by clicking on the popup + // however in the list this is undesirable, so instead show a clickable button + // in case you have a non-expired notification in history (do not disturb mode) + // unless it has the same label as an action + readonly property bool addDefaultAction: (model.hasDefaultAction + && model.defaultActionLabel + && (model.actionLabels || []).indexOf(model.defaultActionLabel) === -1) ? true : false + actionNames: { + var actions = (model.actionNames || []); + if (addDefaultAction) { + actions.unshift("default"); // prepend + } + return actions; + } + actionLabels: { + var labels = (model.actionLabels || []); + if (addDefaultAction) { + labels.unshift(model.defaultActionLabel); + } + return labels; } - } - onDismissClicked: { - model.dismissed = false; - plasmoid.expanded = false; - } - onConfigureClicked: historyModel.configure(historyModel.index(index, 0)) - onActionInvoked: { - if (actionName === "default") { - historyModel.invokeDefaultAction(historyModel.index(index, 0)); - } else { - historyModel.invokeAction(historyModel.index(index, 0), actionName); + onCloseClicked: { + historyModel.close(historyModel.index(index, 0)); + if (list.count === 0) { + root.closePassivePlasmoid(); + } } - // Keep it in the history - historyModel.expire(historyModel.index(index, 0)); - } - onOpenUrl: { - Qt.openUrlExternally(url); - historyModel.expire(historyModel.index(index, 0)); - } - onFileActionInvoked: historyModel.expire(historyModel.index(index, 0)) + onDismissClicked: { + model.dismissed = false; + root.closePassivePlasmoid(); + } + onConfigureClicked: historyModel.configure(historyModel.index(index, 0)) + + onActionInvoked: { + if (actionName === "default") { + historyModel.invokeDefaultAction(historyModel.index(index, 0)); + } else { + historyModel.invokeAction(historyModel.index(index, 0), actionName); + } + // Keep it in the history + historyModel.expire(historyModel.index(index, 0)); + } + onOpenUrl: { + Qt.openUrlExternally(url); + historyModel.expire(historyModel.index(index, 0)); + } + onFileActionInvoked: historyModel.expire(historyModel.index(index, 0)) - onSuspendJobClicked: historyModel.suspendJob(historyModel.index(index, 0)) - onResumeJobClicked: historyModel.resumeJob(historyModel.index(index, 0)) - onKillJobClicked: historyModel.killJob(historyModel.index(index, 0)) + onSuspendJobClicked: historyModel.suspendJob(historyModel.index(index, 0)) + onResumeJobClicked: historyModel.resumeJob(historyModel.index(index, 0)) + onKillJobClicked: historyModel.killJob(historyModel.index(index, 0)) + } } - } - PlasmaComponents.ToolButton { - Layout.preferredWidth: minimumWidth - iconName: model.isGroupExpanded ? "arrow-up" : "arrow-down" - text: model.isGroupExpanded ? i18n("Show Fewer") - : i18nc("Expand to show n more notifications", - "Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount)) - visible: (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded) - && delegateLoader.ListView.nextSection !== delegateLoader.ListView.section - onClicked: list.setGroupExpanded(model.index, !model.isGroupExpanded) - } + PlasmaComponents.ToolButton { + Layout.preferredWidth: minimumWidth + iconName: model.isGroupExpanded ? "arrow-up" : "arrow-down" + text: model.isGroupExpanded ? i18n("Show Fewer") + : i18nc("Expand to show n more notifications", + "Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount)) + visible: (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded) + && delegate.ListView.nextSection !== delegate.ListView.section + onClicked: list.setGroupExpanded(model.index, !model.isGroupExpanded) + } - PlasmaCore.SvgItem { - Layout.fillWidth: true - Layout.bottomMargin: units.smallSpacing - elementId: "horizontal-line" - svg: lineSvg + PlasmaCore.SvgItem { + Layout.fillWidth: true + Layout.bottomMargin: units.smallSpacing + elementId: "horizontal-line" + svg: lineSvg - // property is only atached to the delegate itself (the Loader in our case) - visible: (!model.isInGroup || delegateLoader.ListView.nextSection !== delegateLoader.ListView.section) - && delegateLoader.ListView.nextSection !== "" // don't show after last item + // property is only atached to the delegate itself (the Loader in our case) + visible: (!model.isInGroup || delegate.ListView.nextSection !== delegate.ListView.section) + && delegate.ListView.nextSection !== "" // don't show after last item + } } } } } PlasmaExtras.Heading { width: list.width + height: list.height + horizontalAlignment: Kirigami.Settings.isMobile ? Text.AlignHCenter : Text.AlignLeft + verticalAlignment: Kirigami.Settings.isMobile ? Text.AlignVCenter : Text.AlignTop + wrapMode: Text.WordWrap level: 3 opacity: 0.6 text: i18n("No unread notifications.") - visible: list.count === 0 + visible: list.count === 0 && NotificationManager.Server.valid + } + + ColumnLayout { + id: serverUnavailableColumn + + width: list.width + visible: list.count === 0 && !NotificationManager.Server.valid + + PlasmaExtras.Heading { + Layout.fillWidth: true + level: 3 + opacity: 0.6 + text: i18n("Notification service not available") + wrapMode: Text.WordWrap + } + + PlasmaComponents.Label { + // Checking valid to avoid creating ServerInfo object if everything is alright + readonly property NotificationManager.ServerInfo currentOwner: !NotificationManager.Server.valid ? NotificationManager.Server.currentOwner + : null + + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: currentOwner ? i18nc("Vendor and product name", + "Notifications are currently provided by '%1 %2'", + currentOwner.vendor, + currentOwner.name) + : "" + visible: currentOwner && currentOwner.vendor && currentOwner.name + } } } } diff --git a/applets/notifications/package/contents/ui/JobItem.qml b/applets/notifications/package/contents/ui/JobItem.qml --- a/applets/notifications/package/contents/ui/JobItem.qml +++ b/applets/notifications/package/contents/ui/JobItem.qml @@ -44,6 +44,26 @@ // TOOD make an alias on visible if we're not doing an animation property bool showDetails + readonly property int totalFiles: jobItem.jobDetails && jobItem.jobDetails.totalFiles || 0 + readonly property var url: { + if (jobItem.jobState !== NotificationManager.Notifications.JobStateStopped + || jobItem.jobError + || totalFiles <= 0) { + return null; + } + + // For a single file show actions for it + if (totalFiles === 1) { + return jobItem.jobDetails.descriptionUrl; + // Otherwise the destination folder all of them were copied into + } else { + return jobItem.jobDetails.destUrl; + } + } + + property alias iconContainerItem: jobDragIcon.parent + + readonly property alias dragging: jobDragArea.dragging readonly property alias menuOpen: otherFileActionsMenu.visible signal suspendJobClicked @@ -55,6 +75,43 @@ spacing: 0 + // This item is parented to the NotificationItem iconContainer + PlasmaCore.IconItem { + id: jobDragIcon + width: parent ? parent.width : 0 + height: parent ? parent.height : 0 + usesPlasmaTheme: false + visible: valid + active: jobDragArea.containsMouse + source: jobItem.totalFiles === 1 && jobItem.url ? plasmoid.nativeInterface.iconNameForUrl(jobItem.url) : "" + + Binding { + target: jobDragIcon.parent + property: "visible" + value: true + when: jobDragIcon.valid + } + + DraggableFileArea { + id: jobDragArea + anchors.fill: parent + + hoverEnabled: true + dragParent: jobDragIcon + dragUrl: jobItem.url || "" + dragPixmap: jobDragIcon.source + + onActivated: jobItem.openUrl(jobItem.url) + onContextMenuRequested: { + // avoid menu button glowing if we didn't actually press it + otherFileActionsButton.checked = false; + + otherFileActionsMenu.visualParent = this; + otherFileActionsMenu.open(x, y); + } + } + } + RowLayout { id: progressRow Layout.fillWidth: true @@ -114,30 +171,13 @@ } Flow { // it's a Flow so it can wrap if too long - id: jobDoneActions Layout.fillWidth: true spacing: units.smallSpacing // We want the actions to be right-aligned but Flow also reverses // the order of items, so we put them in reverse order layoutDirection: Qt.RightToLeft visible: url && url.toString() !== "" - property var url: { - if (jobItem.jobState !== NotificationManager.Notifications.JobStateStopped - || jobItem.jobError - || !jobItem.jobDetails - || jobItem.jobDetails.totalFiles <= 0) { - return null; - } - - // For a single file show actions for it - if (jobItem.jobDetails.totalFiles === 1) { - return jobItem.jobDetails.descriptionUrl; - } else { - return jobItem.jobDetails.destUrl; - } - } - PlasmaComponents.Button { id: otherFileActionsButton height: Math.max(implicitHeight, openButton.implicitHeight) @@ -149,14 +189,15 @@ checked = Qt.binding(function() { return otherFileActionsMenu.visible; }); + otherFileActionsMenu.visualParent = this; + // -1 tells it to "align bottom left of visualParent (this)" otherFileActionsMenu.open(-1, -1); } } Notifications.FileMenu { id: otherFileActionsMenu - url: jobDoneActions.url || "" - visualParent: otherFileActionsButton + url: jobItem.url || "" onActionTriggered: jobItem.fileActionInvoked() } } @@ -168,7 +209,7 @@ text: jobItem.jobDetails && jobItem.jobDetails.totalFiles > 1 ? i18nd("plasma_applet_org.kde.plasma.notifications", "Open Containing Folder") : i18nd("plasma_applet_org.kde.plasma.notifications", "Open") - onClicked: jobItem.openUrl(jobDoneActions.url) + onClicked: jobItem.openUrl(jobItem.url) width: minimumWidth } } diff --git a/applets/notifications/package/contents/ui/NotificationHeader.qml b/applets/notifications/package/contents/ui/NotificationHeader.qml --- a/applets/notifications/package/contents/ui/NotificationHeader.qml +++ b/applets/notifications/package/contents/ui/NotificationHeader.qml @@ -30,6 +30,8 @@ import org.kde.kcoreaddons 1.0 as KCoreAddons +import org.kde.quickcharts 1.0 as Charts + import "global" RowLayout { @@ -54,6 +56,9 @@ property int jobState property QtObject jobDetails + property real timeout: 5000 + property real remainingTime: 0 + signal configureClicked signal dismissClicked signal closeClicked @@ -68,6 +73,8 @@ spacing: units.smallSpacing Layout.preferredHeight: Math.max(applicationNameLabel.implicitHeight, units.iconSizes.small) + Component.onCompleted: updateAgoText() + Connections { target: Globals // clock time changed @@ -87,7 +94,7 @@ id: applicationNameLabel Layout.fillWidth: true textFormat: Text.PlainText - elide: Text.ElideRight + elide: Text.ElideLeft text: notificationHeading.applicationName + (notificationHeading.originName ? " · " + notificationHeading.originName : "") } @@ -196,9 +203,37 @@ PlasmaComponents.ToolButton { id: closeButton tooltip: i18nd("plasma_applet_org.kde.plasma.notifications", "Close") - iconSource: "window-close" visible: false onClicked: notificationHeading.closeClicked() + + PlasmaCore.IconItem { + anchors.centerIn: parent + width: units.iconSizes.small + height: width + + source: "window-close" + roundToIconSize: false + active: closeButton.hovered + + Charts.PieChart { + anchors.fill: parent + anchors.margins: -Math.round(units.devicePixelRatio) + + opacity: (notificationHeading.remainingTime > 0 && notificationHeading.remainingTime < notificationHeading.timeout) ? 1 : 0 + Behavior on opacity { + NumberAnimation { duration: units.longDuration } + } + + range { from: 0; to: notificationHeading.timeout; automatic: false } + + valueSources: Charts.SingleValueSource { value: notificationHeading.timeout - notificationHeading.remainingTime } + colorSource: Charts.SingleValueSource { value: "transparent" } + + backgroundColor: theme.highlightColor + + thickness: Math.round(units.devicePixelRatio) * 5 + } + } } } diff --git a/applets/notifications/package/contents/ui/NotificationItem.qml b/applets/notifications/package/contents/ui/NotificationItem.qml --- a/applets/notifications/package/contents/ui/NotificationItem.qml +++ b/applets/notifications/package/contents/ui/NotificationItem.qml @@ -71,24 +71,37 @@ property var actionNames: [] property var actionLabels: [] + property bool hasReplyAction + property string replyActionLabel + property string replyPlaceholderText + property string replySubmitButtonText + property string replySubmitButtonIconName + property int headingLeftPadding: 0 property int headingRightPadding: 0 property int thumbnailLeftPadding: 0 property int thumbnailRightPadding: 0 property int thumbnailTopPadding: 0 property int thumbnailBottomPadding: 0 + property alias timeout: notificationHeading.timeout + property alias remainingTime: notificationHeading.remainingTime + readonly property bool menuOpen: bodyLabel.contextMenu !== null || (thumbnailStripLoader.item && thumbnailStripLoader.item.menuOpen) || (jobLoader.item && jobLoader.item.menuOpen) - readonly property bool dragging: thumbnailStripLoader.item && thumbnailStripLoader.item.dragging + + readonly property bool dragging: (thumbnailStripLoader.item && thumbnailStripLoader.item.dragging) + || (jobLoader.item && jobLoader.item.dragging) + property bool replying: false signal bodyClicked(var mouse) signal closeClicked signal configureClicked signal dismissClicked signal actionInvoked(string actionName) + signal replied(string text) signal openUrl(string url) signal fileActionInvoked @@ -244,6 +257,8 @@ visible: active image: typeof notificationItem.icon === "object" ? notificationItem.icon : undefined } + + // JobItem reparents a file icon here for finished jobs with one total file } } @@ -254,6 +269,8 @@ active: notificationItem.notificationType === NotificationManager.Notifications.JobType visible: active sourceComponent: JobItem { + iconContainerItem: iconContainer + jobState: notificationItem.jobState jobError: notificationItem.jobError percentage: notificationItem.percentage @@ -274,13 +291,46 @@ } } - RowLayout { + Item { + id: actionContainer Layout.fillWidth: true + Layout.preferredHeight: Math.max(actionFlow.implicitHeight, replyLoader.height) visible: actionRepeater.count > 0 + states: [ + State { + when: notificationItem.replying + PropertyChanges { + target: actionFlow + enabled: false + opacity: 0 + } + PropertyChanges { + target: replyLoader + active: true + visible: true + opacity: 1 + x: 0 + } + } + ] + + transitions: [ + Transition { + to: "*" // any state + NumberAnimation { + targets: [actionFlow, replyLoader] + properties: "opacity,scale,x" + duration: units.longDuration + easing.type: Easing.InOutQuad + } + } + ] + // Notification actions Flow { // it's a Flow so it can wrap if too long - Layout.fillWidth: true + id: actionFlow + width: parent.width spacing: units.smallSpacing layoutDirection: Qt.RightToLeft @@ -298,18 +348,54 @@ label: actionLabels[i] }); } + + if (notificationItem.hasReplyAction) { + buttons.unshift({ + actionName: "inline-reply", + label: notificationItem.replyActionLabel || i18nc("Reply to message", "Reply") + }); + } + return buttons; } PlasmaComponents.ToolButton { flat: false // why does it spit "cannot assign undefined to string" when a notification becomes expired? text: modelData.label || "" Layout.preferredWidth: minimumWidth - onClicked: notificationItem.actionInvoked(modelData.actionName) + + onClicked: { + if (modelData.actionName === "inline-reply") { + notificationItem.replying = true; + + plasmoid.nativeInterface.forceActivateWindow(notificationItem.Window.window); + replyLoader.item.activate(); + return; + } + + notificationItem.actionInvoked(modelData.actionName); + } } } } + + // inline reply field + Loader { + id: replyLoader + width: parent.width + height: active ? item.implicitHeight : 0 + active: false + visible: false + opacity: 0 + x: parent.width + sourceComponent: NotificationReplyField { + placeholderText: notificationItem.replyPlaceholderText + buttonIconName: notificationItem.replySubmitButtonIconName + buttonText: notificationItem.replySubmitButtonText + onReplied: notificationItem.replied(text) + } + } } // thumbnails diff --git a/applets/notifications/package/contents/ui/NotificationPopup.qml b/applets/notifications/package/contents/ui/NotificationPopup.qml --- a/applets/notifications/package/contents/ui/NotificationPopup.qml +++ b/applets/notifications/package/contents/ui/NotificationPopup.qml @@ -66,16 +66,25 @@ property alias actionNames: notificationItem.actionNames property alias actionLabels: notificationItem.actionLabels + property alias hasReplyAction: notificationItem.hasReplyAction + property alias replyActionLabel: notificationItem.replyActionLabel + property alias replyPlaceholderText: notificationItem.replyPlaceholderText + property alias replySubmitButtonText: notificationItem.replySubmitButtonText + property alias replySubmitButtonIconName: notificationItem.replySubmitButtonIconName + signal configureClicked signal dismissClicked signal closeClicked signal defaultActionInvoked signal actionInvoked(string actionName) + signal replied(string text) signal openUrl(string url) signal fileActionInvoked signal expired + signal hoverEntered + signal hoverExited signal suspendJobClicked signal resumeJobClicked @@ -93,8 +102,7 @@ } location: PlasmaCore.Types.Floating - - flags: Qt.WindowDoesNotAcceptFocus + flags: notificationItem.replying ? 0 : Qt.WindowDoesNotAcceptFocus visible: false @@ -105,102 +113,89 @@ } } - mainItem: MouseArea { - id: area + mainItem: Item { width: notificationPopup.popupWidth height: notificationItem.implicitHeight + notificationItem.y - hoverEnabled: true - - cursorShape: hasDefaultAction ? Qt.PointingHandCursor : Qt.ArrowCursor - acceptedButtons: hasDefaultAction ? Qt.LeftButton : Qt.NoButton - - onClicked: notificationPopup.defaultActionInvoked() - - LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft - LayoutMirroring.childrenInherit: true - - Timer { - id: timer - interval: notificationPopup.effectiveTimeout - running: notificationPopup.visible && !area.containsMouse && interval > 0 - && !notificationItem.dragging && !notificationItem.menuOpen - onTriggered: { - if (notificationPopup.dismissTimeout) { - notificationPopup.dismissClicked(); - } else { - notificationPopup.expired(); - } - } - } + DraggableDelegate { + id: area + width: parent.width + height: parent.height + hoverEnabled: true + draggable: notificationItem.notificationType != NotificationManager.Notifications.JobType + onDismissRequested: popupNotificationsModel.close(popupNotificationsModel.index(index, 0)) - Timer { - id: timeoutIndicatorDelayTimer - // only show indicator for the last ten seconds of timeout - readonly property int remainingTimeout: 10000 - interval: Math.max(0, timer.interval - remainingTimeout) - running: interval > 0 && timer.running - } + cursorShape: hasDefaultAction ? Qt.PointingHandCursor : Qt.ArrowCursor + acceptedButtons: hasDefaultAction || draggable ? Qt.LeftButton : Qt.NoButton - Rectangle { - id: timeoutIndicatorRect - anchors { - right: parent.right - rightMargin: -notificationPopup.margins.right - bottom: parent.bottom - bottomMargin: -notificationPopup.margins.bottom + onClicked: { + if (hasDefaultAction) { + notificationPopup.defaultActionInvoked(); + } } - width: units.devicePixelRatio * 3 - color: theme.highlightColor - opacity: timeoutIndicatorAnimation.running ? 0.6 : 0 - visible: units.longDuration > 1 - Behavior on opacity { - NumberAnimation { - duration: units.longDuration + onEntered: notificationPopup.hoverEntered() + onExited: notificationPopup.hoverExited() + + LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft + LayoutMirroring.childrenInherit: true + + Timer { + id: timer + interval: notificationPopup.effectiveTimeout + running: notificationPopup.visible && !area.containsMouse && interval > 0 + && !notificationItem.dragging && !notificationItem.menuOpen && !notificationItem.replying + onTriggered: { + if (notificationPopup.dismissTimeout) { + notificationPopup.dismissClicked(); + } else { + notificationPopup.expired(); + } } } NumberAnimation { - id: timeoutIndicatorAnimation - target: timeoutIndicatorRect - property: "height" - from: area.height + notificationPopup.margins.top + notificationPopup.margins.bottom + target: notificationItem + property: "remainingTime" + from: timer.interval to: 0 - duration: Math.min(timer.interval, timeoutIndicatorDelayTimer.remainingTimeout) - running: timer.running && !timeoutIndicatorDelayTimer.running && units.longDuration > 1 + duration: timer.interval + running: timer.running && units.longDuration > 1 } - } - NotificationItem { - id: notificationItem - // let the item bleed into the dialog margins so the close button margins cancel out - y: closable || dismissable || configurable ? -notificationPopup.margins.top : 0 - headingRightPadding: -notificationPopup.margins.right - width: parent.width - hovered: area.containsMouse - maximumLineCount: 8 - bodyCursorShape: notificationPopup.hasDefaultAction ? Qt.PointingHandCursor : 0 - - thumbnailLeftPadding: -notificationPopup.margins.left - thumbnailRightPadding: -notificationPopup.margins.right - thumbnailTopPadding: -notificationPopup.margins.top - thumbnailBottomPadding: -notificationPopup.margins.bottom - - closable: true - onBodyClicked: { - if (area.acceptedButtons & mouse.button) { - area.clicked(null /*mouse*/); + NotificationItem { + id: notificationItem + // let the item bleed into the dialog margins so the close button margins cancel out + y: closable || dismissable || configurable ? -notificationPopup.margins.top : 0 + headingRightPadding: -notificationPopup.margins.right + width: parent.width + hovered: area.containsMouse + maximumLineCount: 8 + bodyCursorShape: notificationPopup.hasDefaultAction ? Qt.PointingHandCursor : 0 + + thumbnailLeftPadding: -notificationPopup.margins.left + thumbnailRightPadding: -notificationPopup.margins.right + thumbnailTopPadding: -notificationPopup.margins.top + thumbnailBottomPadding: -notificationPopup.margins.bottom + + timeout: timer.running ? timer.interval : 0 + + closable: true + onBodyClicked: { + if (area.acceptedButtons & mouse.button) { + area.clicked(null /*mouse*/); + } } + onCloseClicked: notificationPopup.closeClicked() + onDismissClicked: notificationPopup.dismissClicked() + onConfigureClicked: notificationPopup.configureClicked() + onActionInvoked: notificationPopup.actionInvoked(actionName) + onReplied: notificationPopup.replied(text) + onOpenUrl: notificationPopup.openUrl(url) + onFileActionInvoked: notificationPopup.fileActionInvoked() + + onSuspendJobClicked: notificationPopup.suspendJobClicked() + onResumeJobClicked: notificationPopup.resumeJobClicked() + onKillJobClicked: notificationPopup.killJobClicked() } - onCloseClicked: notificationPopup.closeClicked() - onDismissClicked: notificationPopup.dismissClicked() - onConfigureClicked: notificationPopup.configureClicked() - onActionInvoked: notificationPopup.actionInvoked(actionName) - onOpenUrl: notificationPopup.openUrl(url) - onFileActionInvoked: notificationPopup.fileActionInvoked() - - onSuspendJobClicked: notificationPopup.suspendJobClicked() - onResumeJobClicked: notificationPopup.resumeJobClicked() - onKillJobClicked: notificationPopup.killJobClicked() } } } diff --git a/applets/notifications/package/contents/ui/NotificationReplyField.qml b/applets/notifications/package/contents/ui/NotificationReplyField.qml new file mode 100644 --- /dev/null +++ b/applets/notifications/package/contents/ui/NotificationReplyField.qml @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Kai Uwe Broulik + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import QtQuick 2.8 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.components 2.0 as PlasmaComponents + +RowLayout { + id: replyRow + + signal replied(string text) + + property alias text: replyTextField.text + property string placeholderText + property string buttonIconName + property string buttonText + + spacing: units.smallSpacing + + function activate() { + replyTextField.forceActiveFocus(); + } + + PlasmaComponents.TextField { + id: replyTextField + Layout.fillWidth: true + placeholderText: replyRow.replyPlaceholderText + || i18ndc("plasma_applet_org.kde.plasma.notifications", "Text field placeholder", "Type a reply...") + onAccepted: { + if (replyButton.enabled) { + replyRow.replied(text); + } + } + } + + PlasmaComponents.Button { + id: replyButton + Layout.preferredWidth: minimumWidth + iconName: replyRow.buttonIconName || "document-send" + text: replyRow.buttonText + || i18ndc("plasma_applet_org.kde.plasma.notifications", "@action:button", "Send") + enabled: replyTextField.length > 0 + onClicked: replyRow.replied(replyTextField.text) + } +} diff --git a/applets/notifications/package/contents/ui/SelectableLabel.qml b/applets/notifications/package/contents/ui/SelectableLabel.qml --- a/applets/notifications/package/contents/ui/SelectableLabel.qml +++ b/applets/notifications/package/contents/ui/SelectableLabel.qml @@ -20,11 +20,13 @@ */ import QtQuick 2.8 +import QtQuick.Window 2.2 import QtQuick.Layouts 1.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.kirigami 2.11 as Kirigami // NOTE This wrapper item is needed for QQC ScrollView to work // In NotificationItem we just do SelectableLabel {} and then it gets confused @@ -45,6 +47,7 @@ implicitWidth: bodyText.paintedWidth implicitHeight: bodyText.paintedHeight + PlasmaExtras.ScrollArea { id: bodyTextScrollArea @@ -59,7 +62,7 @@ width: bodyTextScrollArea.width // TODO check that this doesn't causes infinite loops when it starts adding and removing the scrollbar //width: bodyTextScrollArea.viewport.width - //enabled: !Settings.isMobile + enabled: !Kirigami.Settings.isMobile color: PlasmaCore.ColorScope.textColor selectedTextColor: theme.viewBackgroundColor @@ -73,8 +76,12 @@ font.underline: theme.defaultFont.underline font.weight: theme.defaultFont.weight font.wordSpacing: theme.defaultFont.wordSpacing - renderType: Text.NativeRendering - selectByMouse: true + // Work around Qt bug where NativeRendering breaks for non-integer scale factors + // https://bugreports.qt.io/browse/QTBUG-67007 + renderType: Screen.devicePixelRatio % 1 !== 0 ? Text.QtRendering : Text.NativeRendering + // Selectable only when we are in desktop mode + selectByMouse: !Kirigami.Settings.tabletMode + readOnly: true wrapMode: Text.Wrap textFormat: TextEdit.RichText diff --git a/applets/notifications/package/contents/ui/ThumbnailStrip.qml b/applets/notifications/package/contents/ui/ThumbnailStrip.qml --- a/applets/notifications/package/contents/ui/ThumbnailStrip.qml +++ b/applets/notifications/package/contents/ui/ThumbnailStrip.qml @@ -29,16 +29,15 @@ import org.kde.plasma.private.notifications 2.0 as Notifications -MouseArea { +DraggableFileArea { id: thumbnailArea // The protocol supports multiple URLs but so far it's only used to show - // a single preview image, so this code is simplified a lot to accomodate + // a single preview image, so this code is simplified a lot to accommodate // this usecase and drops everything else (fallback to app icon or ListView // for multiple files) property var urls - readonly property bool dragging: plasmoid.nativeInterface.dragActive readonly property alias menuOpen: fileMenu.visible property int _pressX: -1 @@ -52,48 +51,21 @@ signal openUrl(string url) signal fileActionInvoked + dragParent: previewPixmap + dragUrl: thumbnailer.url + dragPixmap: thumbnailer.pixmap + implicitHeight: Math.max(menuButton.height + 2 * menuButton.anchors.topMargin, Math.round(Math.min(width / 3, width / thumbnailer.ratio))) + topPadding + bottomPadding - preventStealing: true - cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton - - onClicked: { - if (mouse.button === Qt.LeftButton) { - thumbnailArea.openUrl(thumbnailer.url) - } - } - - onPressed: { - if (mouse.button === Qt.LeftButton) { - _pressX = mouse.x; - _pressY = mouse.y; - } else if (mouse.button === Qt.RightButton) { - // avoid menu button glowing if we didn't actually press it - menuButton.checked = false; + onActivated: thumbnailArea.openUrl(thumbnailer.url) + onContextMenuRequested: { + // avoid menu button glowing if we didn't actually press it + menuButton.checked = false; - fileMenu.visualParent = this; - fileMenu.open(mouse.x, mouse.y); - } - } - onPositionChanged: { - if (_pressX !== -1 && _pressY !== -1 && plasmoid.nativeInterface.isDrag(_pressX, _pressY, mouse.x, mouse.y)) { - plasmoid.nativeInterface.startDrag(previewPixmap, thumbnailer.url, thumbnailer.pixmap); - _pressX = -1; - _pressY = -1; - } - } - onReleased: { - _pressX = -1; - _pressY = -1; - } - onContainsMouseChanged: { - if (!containsMouse) { - _pressX = -1; - _pressY = -1; - } + fileMenu.visualParent = this; + fileMenu.open(x, y); } Notifications.FileMenu { diff --git a/applets/notifications/package/contents/ui/global/Globals.qml b/applets/notifications/package/contents/ui/global/Globals.qml --- a/applets/notifications/package/contents/ui/global/Globals.qml +++ b/applets/notifications/package/contents/ui/global/Globals.qml @@ -29,6 +29,8 @@ import org.kde.notificationmanager 1.0 as NotificationManager +import org.kde.plasma.private.notifications 2.0 as Notifications + import ".." // This singleton object contains stuff shared between all notification plasmoids, namely: @@ -126,9 +128,12 @@ } } + readonly property QtObject focusDialog: plasmoid.nativeInterface.focussedPlasmaDialog + onFocusDialogChanged: positionPopups() + // The raw width of the popup's content item, the Dialog itself adds some margins property int popupWidth: units.gridUnit * 18 - property int popupEdgeDistance: units.largeSpacing + property int popupEdgeDistance: units.largeSpacing * 2 property int popupSpacing: units.largeSpacing // How much vertical screen real estate the notification popups may consume @@ -195,6 +200,10 @@ globals.inhibited = Qt.binding(function() { var inhibited = false; + if (!NotificationManager.Server.valid) { + return false; + } + var inhibitedUntil = notificationSettings.notificationsInhibitedUntil; if (!isNaN(inhibitedUntil.getTime())) { inhibited |= (new Date().getTime() < inhibitedUntil.getTime()); @@ -204,10 +213,30 @@ inhibited |= true; } + if (notificationSettings.inhibitNotificationsWhenScreensMirrored) { + inhibited |= notificationSettings.screensMirrored; + } + return inhibited; }); } + function revokeInhibitions() { + notificationSettings.notificationsInhibitedUntil = undefined; + notificationSettings.revokeApplicationInhibitions(); + // overrules current mirrored screen setup, updates again when screen configuration changes + notificationSettings.screensMirrored = false; + + notificationSettings.save(); + } + + function rectIntersect(rect1 /*dialog*/, rect2 /*popup*/) { + return rect1.x < rect2.x + rect2.width + && rect2.x < rect1.x + rect1.width + && rect1.y < rect2.y + rect2.height + && rect2.y < rect1.y + rect1.height; + } + function positionPopups() { if (!plasmoid) { return; @@ -223,7 +252,7 @@ var y = screenRect.y; if (popupLocation & Qt.AlignBottom) { - y += screenRect.height; + y += screenRect.height - popupEdgeDistance; } else { y += popupEdgeDistance; } @@ -234,7 +263,7 @@ } for (var i = 0; i < popupInstantiator.count; ++i) { - var popup = popupInstantiator.objectAt(i); + let popup = popupInstantiator.objectAt(i); // Popup width is fixed, so don't rely on the actual window size var popupEffectiveWidth = popupWidth + popup.margins.left + popup.margins.right; @@ -246,16 +275,27 @@ popup.x = x; } - // If the popup isn't ready yet, ignore its occupied space for now. - // We'll reposition everything in onHeightChanged eventually. - var delta = popup.height + (popup.height > 0 ? popupSpacing : 0); - if (popupLocation & Qt.AlignTop) { + // We want to calculate the new position based on its original target position to avoid positioning it and then + // positioning it again, hence the temporary Qt.rect with explicit "y" and not just the popup as a whole + if (focusDialog && focusDialog.visible && focusDialog !== popup + && rectIntersect(focusDialog, Qt.rect(popup.x, y, popup.width, popup.height))) { + y = focusDialog.y + focusDialog.height + popupEdgeDistance; + } popup.y = y; - y += delta; + // If the popup isn't ready yet, ignore its occupied space for now. + // We'll reposition everything in onHeightChanged eventually. + y += popup.height + (popup.height > 0 ? popupSpacing : 0); } else { - y -= delta; + y -= popup.height; + if (focusDialog && focusDialog.visible && focusDialog !== popup + && rectIntersect(focusDialog, Qt.rect(popup.x, y, popup.width, popup.height))) { + y = focusDialog.y - popup.height - popupEdgeDistance; + } popup.y = y; + if (popup.height > 0) { + y -= popupSpacing; + } } // don't let notifications take more than popupMaximumScreenFill of the screen @@ -268,7 +308,6 @@ } } - // TODO would be nice to hide popups when systray or panel controller is open popup.visible = visible; } } @@ -377,7 +416,14 @@ actionNames: model.actionNames actionLabels: model.actionLabels + hasReplyAction: model.hasReplyAction || false + replyActionLabel: model.replyActionLabel || "" + replyPlaceholderText: model.replyPlaceholderText || "" + replySubmitButtonText: model.replySubmitButtonText || "" + replySubmitButtonIconName: model.replySubmitButtonIconName || "" + onExpired: popupNotificationsModel.expire(popupNotificationsModel.index(index, 0)) + onHoverEntered: model.read = true onCloseClicked: popupNotificationsModel.close(popupNotificationsModel.index(index, 0)) onDismissClicked: model.dismissed = true onConfigureClicked: popupNotificationsModel.configure(popupNotificationsModel.index(index, 0)) @@ -389,6 +435,10 @@ popupNotificationsModel.invokeAction(popupNotificationsModel.index(index, 0), actionName) popupNotificationsModel.close(popupNotificationsModel.index(index, 0)) } + onReplied: { + popupNotificationsModel.reply(popupNotificationsModel.index(index, 0), text); + popupNotificationsModel.close(popupNotificationsModel.index(index, 0)); + } onOpenUrl: { Qt.openUrlExternally(url); popupNotificationsModel.close(popupNotificationsModel.index(index, 0)) @@ -444,4 +494,32 @@ interval: 250 onTriggered: positionPopups() } + + // Keeps the Inhibited property on DBus in sync with our inhibition handling + property Binding serverInhibitedBinding: Binding { + target: NotificationManager.Server + property: "inhibited" + value: globals.inhibited + } + + property Notifications.GlobalShortcuts shortcuts: Notifications.GlobalShortcuts { + onToggleDoNotDisturbTriggered: { + var oldInhibited = globals.inhibited; + if (oldInhibited) { + globals.revokeInhibitions(); + } else { + // Effectively "in a year" is "until turned off" + var d = new Date(); + d.setFullYear(d.getFullYear() + 1); + notificationSettings.notificationsInhibitedUntil = d; + notificationSettings.save(); + } + + checkInhibition(); + + if (globals.inhibited !== oldInhibited) { + showDoNotDisturbOsd(globals.inhibited); + } + } + } } diff --git a/applets/notifications/package/contents/ui/main.qml b/applets/notifications/package/contents/ui/main.qml --- a/applets/notifications/package/contents/ui/main.qml +++ b/applets/notifications/package/contents/ui/main.qml @@ -34,36 +34,59 @@ Item { id: root - Plasmoid.status: historyModel.activeJobsCount > 0 + readonly property int effectiveStatus: historyModel.activeJobsCount > 0 || historyModel.unreadNotificationsCount > 0 || Globals.inhibited ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.PassiveStatus + onEffectiveStatusChanged: { + if (effectiveStatus === PlasmaCore.Types.PassiveStatus) { + // HACK System Tray only lets applets self-hide when in Active state + // When we clear the notifications, the status is updated right away + // as a result of model signals, and when we then try to collapse + // the popup isn't hidden. + Qt.callLater(function() { + Plasmoid.status = effectiveStatus; + }); + } else { + Plasmoid.status = effectiveStatus; + } + } + + Plasmoid.status: effectiveStatus Plasmoid.toolTipSubText: { var lines = []; if (historyModel.activeJobsCount > 0) { lines.push(i18np("%1 running job", "%1 running jobs", historyModel.activeJobsCount)); } - // Any notification that is newer than "lastRead" is "unread" - // since it doesn't know the popup is on screen which makes the user see it - var actualUnread = historyModel.unreadNotificationsCount - Globals.popupNotificationsModel.activeNotificationsCount; - if (actualUnread > 0) { - lines.push(i18np("%1 unread notification", "%1 unread notifications", actualUnread)); - } - - if (Globals.inhibited) { - var inhibitedUntil = notificationSettings.notificationsInhibitedUntil - var inhibitedUntilValid = !isNaN(inhibitedUntil.getTime()); + if (!NotificationManager.Server.valid) { + lines.push(i18n("Notification service not available")); + } else { + // Any notification that is newer than "lastRead" is "unread" + // since it doesn't know the popup is on screen which makes the user see it + var actualUnread = historyModel.unreadNotificationsCount - Globals.popupNotificationsModel.activeNotificationsCount; + if (actualUnread > 0) { + lines.push(i18np("%1 unread notification", "%1 unread notifications", actualUnread)); + } - // TODO check app inhibition, too - if (inhibitedUntilValid) { - lines.push(i18n("Do not disturb until %1", - KCoreAddons.Format.formatRelativeDateTime(inhibitedUntil, Locale.ShortFormat))); + if (Globals.inhibited) { + var inhibitedUntil = notificationSettings.notificationsInhibitedUntil + var inhibitedUntilValid = !isNaN(inhibitedUntil.getTime()); + + // Show until time if valid but not if too far in the future + // TODO check app inhibition, too + if (inhibitedUntilValid + && inhibitedUntil.getTime() - new Date().getTime() < 365 * 24 * 60 * 60 * 1000 /* 1 year*/) { + lines.push(i18n("Do not disturb until %1", + KCoreAddons.Format.formatRelativeDateTime(inhibitedUntil, Locale.ShortFormat))); + } else { + lines.push(i18n("Do not disturb")); + } + } else if (lines.length === 0) { + lines.push(i18n("No unread notifications")); } - } else if (lines.length === 0) { - lines.push(i18n("No unread notifications")); } return lines.join("\n"); @@ -77,11 +100,8 @@ Plasmoid.onExpandedChanged: { if (!plasmoid.expanded) { - // FIXME Qt.callLater because system tray gets confused when an applet becomes passive when clicking to hide it - Qt.callLater(function() { - historyModel.lastRead = undefined; // reset to now - historyModel.collapseAllGroups(); - }); + historyModel.lastRead = undefined; // reset to now + historyModel.collapseAllGroups(); } } @@ -92,7 +112,7 @@ jobsCount: historyModel.activeJobsCount jobsPercentage: historyModel.jobsPercentage - inhibited: Globals.inhibited + inhibited: Globals.inhibited || !NotificationManager.Server.valid } Plasmoid.fullRepresentation: FullRepresentation { @@ -124,10 +144,22 @@ } } + Binding { + target: plasmoid.nativeInterface + property: "dragPixmapSize" + value: units.iconSizes.large + } + + function closePassivePlasmoid() { + if (plasmoid.status !== PlasmaCore.Types.PassiveStatus) { + plasmoid.expanded = false; + } + } + function action_clearHistory() { historyModel.clear(NotificationManager.Notifications.ClearExpired); if (historyModel.count === 0) { - plasmoid.expanded = false; + closePassivePlasmoid(); } } diff --git a/applets/notifications/package/metadata.desktop b/applets/notifications/package/metadata.desktop --- a/applets/notifications/package/metadata.desktop +++ b/applets/notifications/package/metadata.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Notifications Name[ar]=إخطارات +Name[ast]=Avisos Name[be]=Абвяшчэнні Name[be@latin]=Infarmavańnie Name[bg]=Уведомяване @@ -10,7 +11,7 @@ Name[bs]=Obavještenja Name[ca]=Notificacions Name[ca@valencia]=Notificacions -Name[cs]=Oznamování +Name[cs]=Upozornění Name[csb]=Dôwanié wiédzë Name[da]=Bekendtgørelser Name[de]=Benachrichtigungen @@ -73,7 +74,7 @@ Name[sv]=Underrättelser Name[ta]=Notifications Name[te]=నోటీసులు -Name[tg]=Огоҳиномаҳо +Name[tg]=Огоҳиҳо Name[th]=การแจ้งให้ทราบต่าง ๆ Name[tr]=Bildirimler Name[ug]=ئۇقتۇرۇشلار @@ -90,14 +91,14 @@ Comment[bs]=Prikazuje obavještenja i poslove Comment[ca]=Mostra les notificacions i els treballs Comment[ca@valencia]=Mostra les notificacions i els treballs -Comment[cs]=Oznámení a úlohy +Comment[cs]=Upozornění a úlohy Comment[da]=Vis bekendtgørelser og job Comment[de]=Benachrichtigungen und Aktionen anzeigen Comment[el]=Εμφανίζει ειδοποιήσεις και εργασίες Comment[en_GB]=Display notifications and jobs Comment[es]=Mostrar notificaciones y tareas Comment[et]=Märguannete ja tööde näitamine -Comment[eu]=Bistaratu jakinarazpenak eta lanak +Comment[eu]=Azaldu jakinarazpenak eta atazak Comment[fi]=Näyttää ilmoituksia ja töitä Comment[fr]=Affiche les notifications et les tâches Comment[ga]=Taispeáin fógraí agus jabanna @@ -135,7 +136,6 @@ Comment[sr@ijekavianlatin]=Prikazuje obavještenja i poslove Comment[sr@latin]=Prikazuje obaveštenja i poslove Comment[sv]=Visa underrättelser och jobb -Comment[tg]=Иттилооти огоҳиҳо ва амалҳо Comment[th]=แสดงการแจ้งให้ทราบและงานต่าง ๆ Comment[tr]=Bildirimleri ve görevleri göster Comment[ug]=ئۇقتۇرۇش ۋە ۋەزىپىلەرنى كۆرسىتىدۇ diff --git a/applets/panelspacer/metadata.desktop b/applets/panelspacer/metadata.desktop --- a/applets/panelspacer/metadata.desktop +++ b/applets/panelspacer/metadata.desktop @@ -13,7 +13,7 @@ Name[eo]=Spacigilo de Panelo Name[es]=Espaciador Name[et]=Paneeliruumi korraldaja -Name[eu]=Panel-bereizlea +Name[eu]=Paneleko bereizlea Name[fi]=Paneelivälilevy Name[fr]=Espaceur de tableau de bord Name[fy]=Paniel spaasje @@ -23,6 +23,7 @@ Name[hr]=Razmak na traci Name[hu]=Panelelválasztó Name[ia]=Spatiator de pannello +Name[id]=Panel Spacer Name[is]=Spjaldabil Name[it]=Spaziatore del pannello Name[ja]=パネルのスペーサー @@ -54,7 +55,6 @@ Name[sr@ijekavianlatin]=panelska razmaknica Name[sr@latin]=panelska razmaknica Name[sv]=Panelavskiljare -Name[tg]=Фосилаи панел Name[th]=ตัวสร้างพื้นที่ว่าง Name[tr]=Panel Ayırıcı Name[ug]=تاختا ئارىلىقى @@ -77,7 +77,7 @@ Comment[eo]=Rezervi malplenan spacon en la panelo. Comment[es]=Reservar espacios vacíos en el panel. Comment[et]=Tühja ruumi reserveerimine paneelil. -Comment[eu]=Erreserbatu panelaren barruko leku hutsak. +Comment[eu]=Gorde leku hutsak panel barruan. Comment[fi]=Varaa tyhjiä tiloja paneelin sisälle. Comment[fr]=Réserve des espaces vides dans le tableau de bord Comment[fy]=Romte yn paniel frijhâlde @@ -95,7 +95,7 @@ Comment[km]=បម្រុងទុក​ចន្លោះទំនេរ​នៅ​ក្នុង​បន្ទះ ។ Comment[kn]=ಫಲಕದ ಒಳಗೆ ಖಾಲಿ ಜಾಗಗಳನ್ನು ಕಾದಿರಿಸು. Comment[ko]=패널의 빈 공간을 채웁니다. -Comment[lt]=Skirtukas tuščios vietos sukūrimui pulte. +Comment[lt]=Rezervuoti skydelyje tuščią vietą. Comment[lv]=Aizpilda pieejamo tukšo vietu uz paneļa. Comment[mai]=पटलक अंदर रिक्त स्थान खाली राखू. Comment[mk]=Резервира празни места во рамките на панелот diff --git a/applets/systemmonitor/common/contents/config/main.xml b/applets/systemmonitor/common/contents/config/main.xml --- a/applets/systemmonitor/common/contents/config/main.xml +++ b/applets/systemmonitor/common/contents/config/main.xml @@ -13,6 +13,14 @@ 2000 + + + + + + + 1 + diff --git a/applets/systemmonitor/common/contents/ui/ConfigGeneral.qml b/applets/systemmonitor/common/contents/ui/ConfigGeneral.qml --- a/applets/systemmonitor/common/contents/ui/ConfigGeneral.qml +++ b/applets/systemmonitor/common/contents/ui/ConfigGeneral.qml @@ -17,7 +17,6 @@ */ import QtQuick 2.5 -import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.3 @@ -119,23 +118,28 @@ anchors.left: parent.left anchors.right: parent.right - // QQC2 SpinBox doesn't cleanly support non-integer values, which can be - // worked around, but the code is messy and the user experience is - // somewhat poor. So for now, we stick with the QQC1 SpinBox - QQC1.SpinBox { + QQC2.SpinBox { id: updateIntervalSpinBox - Kirigami.FormData.label: i18n("Update interval:") - decimals: 1 - suffix: i18ncp("Suffix for spinbox (seconds)", " second", - " seconds") - maximumValue: 1000 - stepSize: 0.1 - onValueChanged: cfg_updateInterval = value * 1000 - Component.onCompleted: value = cfg_updateInterval / 1000 + from: 100 + stepSize: 100 + to: 1000000 + editable: true + validator: DoubleValidator { + bottom: spinbox.from + top: spinbox.to + } + textFromValue: function(value) { + var seconds = value / 1000 + return i18ncp("SpinBox text", "%1 second", "%1 seconds", seconds.toFixed(1)) + } + valueFromText: function(text) { + return parseFloat(text) * 1000 + } + value: cfg_updateInterval + onValueModified: cfg_updateInterval = value } - Item { Kirigami.FormData.isSection: true } @@ -160,8 +164,10 @@ } } else { var idx = cfg_sources.indexOf(model.source); - if (idx !== -1) { - cfg_sources.splice(idx, 1); + if (cfg_sources.length !== 1) { // This condition prohibits turning off the last item from the list. + if (idx !== -1) { + cfg_sources.splice(idx, 1); + } } } cfg_sourcesChanged(); diff --git a/applets/systemmonitor/cpu/Messages.sh b/applets/systemmonitor/cpu/Messages.sh --- a/applets/systemmonitor/cpu/Messages.sh +++ b/applets/systemmonitor/cpu/Messages.sh @@ -1,2 +1,2 @@ #! /usr/bin/env bash -$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.systemmonitor.cpu.pot +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` `find ../common -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.systemmonitor.cpu.pot diff --git a/applets/systemmonitor/cpu/metadata.desktop b/applets/systemmonitor/cpu/metadata.desktop --- a/applets/systemmonitor/cpu/metadata.desktop +++ b/applets/systemmonitor/cpu/metadata.desktop @@ -13,15 +13,16 @@ Name[eu]=PUZ zamaren begiralea Name[fi]=Suoritinkäyttö Name[fr]=Surveillance du processeur -Name[gl]=Monitor da carga da CPU +Name[gl]=Vixilante da carga da CPU Name[he]=מנטר מעבד Name[hu]=Processzorfigyelő Name[id]=Pemantau Muatan CPU Name[is]=Eftirlit með notkun örgjörva Name[it]=Monitor del carico del processore Name[ja]=CPU 負荷モニタ Name[ko]=CPU 사용량 모니터 -Name[lt]=CPU apkrovos stebėtojas +Name[lt]=Procesoriaus apkrovos prižiūryklė +Name[lv]=CPU slodzes monitors Name[nl]=Monitor voor CPU-gebruik Name[nn]=Prosessorlast-overvaking Name[pa]=CPU ਲੋਡ ਲਈ ਨਿਗਰਾਨ @@ -36,13 +37,15 @@ Name[sr@ijekavianlatin]=nadzor opterećenja procesora Name[sr@latin]=nadzor opterećenja procesora Name[sv]=Övervakning av processorlast +Name[tg]=Назорати боршавии CPU Name[tr]=İşlemci Yükü İzleyici Name[uk]=Використання процесорів Name[x-test]=xxCPU Load Monitorxx Name[zh_CN]=CPU 负载监视器 Name[zh_TW]=CPU 負載監視器 Comment=Monitor the load of the CPUs Comment[ar]=راقِب حِمل المعالجات لديك +Comment[ast]=Supervisa la carga de les CPUs Comment[ca]=Controla la càrrega de les CPU Comment[ca@valencia]=Controla la càrrega de les CPU Comment[cs]=Monitorovat zatížení procesor(ů) @@ -55,15 +58,15 @@ Comment[eu]=Gainbegiratu PUZen zama Comment[fi]=Seuraa suoritinten kuormaa Comment[fr]=Surveille la charge des processeurs -Comment[gl]=Monitorizar a carga das CPU. +Comment[gl]=Vixiar a carga das CPU. Comment[he]=צג שימוש במעבד Comment[hu]=A processzorterhelés monitorozása Comment[id]=Pemantau muatan CPU Comment[is]=Fylgjast með álagi á örgjörva Comment[it]=Indica il carico dei processori Comment[ja]=CPU の負荷を監視します Comment[ko]=CPU 사용량 모니터 -Comment[lt]=Stebėti CPU apkrovą +Comment[lt]=Stebėti procesorių apkrovą Comment[nl]=Het gebruik van de CPU's monitoren Comment[nn]=Overvaking av prosessorbruk Comment[pa]=CPU ਦੇ ਲੋਡ ਦੀ ਨਿਗਰਾਨੀ diff --git a/applets/systemmonitor/diskactivity/Messages.sh b/applets/systemmonitor/diskactivity/Messages.sh --- a/applets/systemmonitor/diskactivity/Messages.sh +++ b/applets/systemmonitor/diskactivity/Messages.sh @@ -1,2 +1,2 @@ #! /usr/bin/env bash -$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.systemmonitor.diskactivity.pot +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` `find ../common -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.systemmonitor.diskactivity.pot diff --git a/applets/systemmonitor/diskactivity/metadata.desktop b/applets/systemmonitor/diskactivity/metadata.desktop --- a/applets/systemmonitor/diskactivity/metadata.desktop +++ b/applets/systemmonitor/diskactivity/metadata.desktop @@ -3,19 +3,19 @@ Name[ar]=مراقب د/خ القرص الصلب Name[bs]=Hard Disk I/O Monitor Name[ca]=Monitor d'E/S de disc dur -Name[ca@valencia]=Monitor d'E/S de disc dur +Name[ca@valencia]=Monitor d'E/E de disc dur Name[cs]=Monitor I/O pevného disku Name[da]=Overvågning af harddisk-I/O Name[de]=Überwachungsmonitor für Festplattenein- und ausgabe Name[el]=Επόπτης χρήσης του σκληρού δίσκου Name[en_GB]=Hard Disk I/O Monitor Name[es]=Monitor de E/S del disco duro Name[et]=Kõvaketta sisendi/väljundi jälgija -Name[eu]=Disko gogorreko S/I monitorea +Name[eu]=Disko zurrun S/I begiralea Name[fi]=Kiintolevyjen käyttöilmaisin Name[fr]=Surveillance des disques durs (E/S) Name[ga]=Monatóir I/A Diosca Crua -Name[gl]=Monitor da E/S do disco duro +Name[gl]=Vixilante da E/S do disco duro Name[he]=מנטר שימוש בכונן הקשיח Name[hu]=Merevlemez I/O monitor Name[ia]=Monitor de I/E del disco dur @@ -26,7 +26,8 @@ Name[kk]=Қатқыл дискінің Е/Ш бақылауы Name[km]=ត្រួតពិនិត្យ I/O ថាសរឹង Name[ko]=하드디스크 I/O 모니터 -Name[lt]=Standžiojo disko I/O stebėtojas +Name[lt]=Standžiojo disko I/O prižiūryklė +Name[lv]=Cietā diska noslogojuma novērotājs Name[mr]=हार्ड डिस्क I/O नियंत्रक Name[nb]=En overvåker for disk-I/U Name[nds]=Fastplaat-I/O-Beluern @@ -45,6 +46,7 @@ Name[sr@ijekavianlatin]=Nadgledanje U/I‑ja hard‑diska Name[sr@latin]=Nadgledanje U/I‑ja hard‑diska Name[sv]=Övervakning av in- och utmatning för hårddisk +Name[tg]=Назорати диски компютерӣ I/O Name[tr]=Sabit Disk G/Ç İzleyici Name[uk]=Монітор роботи жорсткого диска Name[vi]=Trình quản lý I/O đĩa cứng @@ -55,29 +57,29 @@ Comment[ar]=بُريْمج يراقب استخدام إنتاجيّة القرص الصلب ودَخْله/خَرْجه Comment[bs]=Aplet koji prati hard disk protok ulaz / izlaz Comment[ca]=Una miniaplicació que controla la velocitat de transferència, així com l'entrada/sortida de les dades al disc dur -Comment[ca@valencia]=Una miniaplicació que controla la velocitat de transferència, així com l'entrada/eixida de les dades al disc dur +Comment[ca@valencia]=Una miniaplicació que controla la velocitat de transferència, així com l'entrada/eixida de les dades en el disc dur Comment[cs]=Aplet, jenž monitoruje propustnost disku a vstup/výstup Comment[da]=En applet som overvåger gennemgang og input/output for harddisken Comment[de]=Ein Miniprogramm, das den Festplattendurchsatz und die Festplattenein- und -ausgabe überwacht Comment[el]=Μικροεφαρμογή που εποπτεύει τη ρυθμοαπόδοση και την είσοδο/έξοδο του σκληρού δίσκου Comment[en_GB]=An applet that monitors hard disk throughput and input/output Comment[es]=Una miniaplicación que monitoriza el rendimiento y la entrada/salida del disco duro Comment[et]=Kõvaketta läbilaset ja sisendit/väljundit jälgiv aplett -Comment[eu]=Disko gogorraren errendimendua eta sarrera/irteera kontrolatzen dituen miniaplikazio bat +Comment[eu]=Disko zurrunaren transferentzia tasa eraginkorra («throughput») eta sarrera/irteera gainbegiratzen dituen aplikaziotxo bat Comment[fi]=Tarkkailee levyjen suoritus­tehoa ja siirtomäärää Comment[fr]=Une applet surveillant l'activité des disques durs et de leurs entrées / sorties -Comment[gl]=Un miniaplicativo que monitoriza o rendemento e a entrada e saída +Comment[gl]=Un trebello que vixía o rendemento e a entrada e saída Comment[he]=ישומון למעקב אחר מהירות הכתיבה והקריאה בכונן Comment[hu]=Egy kisalkalmazás, amely figyeli a merevlemez átvitelét és a bemenetet/kimenetet Comment[ia]=Un applet que que monitora le prestation (throughput) e ingresso/egresso de disco dur -Comment[id]=Sebuah applet yang memantau lalu-lalang dan intput/output hard disk +Comment[id]=Sebuah applet yang memantau lalu-lalang dan input/output hard disk Comment[is]=Smáforrit sem fylgist með afköstum harðra diska og ílagi/frálagi Comment[it]=Un'applet che controlla le prestazioni e l'uso del disco fisso Comment[ja]=ハードディスクのスループットと入力/出力を監視するアプレット Comment[kk]=Дискінің белсендігі және енгізу/шығаруды бақылау апплеті Comment[km]=អាប់ភ្លេត​ដែល​ត្រួតពិនិត្យ​ថាសរឹង​ និង​ឧបករណ៍​ចេញ/ចូល Comment[ko]=하드디스크 대역폭 및 I/O 상태를 보여 주는 애플릿 -Comment[lt]=Programėlė, kuri stebi disko apkrovimą ir įvestį/išvestį +Comment[lt]=Programėlė, kuri stebi disko apkrovą ir įvedimą/išvedimą Comment[mr]=हार्ड डिस्क इनपुट व आउटपुट दर्शविणारे एप्लेट Comment[nb]=Et miniprogram som overvåker dataflyt til og fra harddisk Comment[nds]=En Lüttprogramm, dat den Fastplaat-Dörsatz un de In- un Utgaven beluert diff --git a/applets/systemmonitor/diskusage/Messages.sh b/applets/systemmonitor/diskusage/Messages.sh --- a/applets/systemmonitor/diskusage/Messages.sh +++ b/applets/systemmonitor/diskusage/Messages.sh @@ -1,2 +1,2 @@ #! /usr/bin/env bash -$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.systemmonitor.diskusage.pot +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` `find ../common -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.systemmonitor.diskusage.pot diff --git a/applets/systemmonitor/diskusage/metadata.desktop b/applets/systemmonitor/diskusage/metadata.desktop --- a/applets/systemmonitor/diskusage/metadata.desktop +++ b/applets/systemmonitor/diskusage/metadata.desktop @@ -11,7 +11,7 @@ Name[en_GB]=Hard Disk Space Usage Name[es]=Espacio usado en el disco duro Name[et]=Kõvaketta ruumikasutus -Name[eu]=Disko gogorreko lekuaren erabilera +Name[eu]=Disko zurruneko espazioaren erabilera Name[fi]=Kiintolevyjen tilankäyttö Name[fr]=Utilisation de l'espace des disques dur Name[gl]=Uso do espazo do disco duro @@ -25,7 +25,8 @@ Name[kk]=Қатқыл дискідегі пайдаланған орын Name[km]=ការ​ប្រើ​ប្រាស់​ទំហំ​ថាសរឹង Name[ko]=하드디스크 공간 사용량 -Name[lt]=Standžiojo disko panaudojimas +Name[lt]=Standžiojo disko vietos panaudojimas +Name[lv]=Cietā diska telpas lietojums Name[mr]=हार्ड डिस्क वापर Name[nb]=Plassbruk for harddisk Name[nds]=Fastplaat-Bruuk @@ -54,18 +55,18 @@ Comment[ar]=بُريْمج يراقب استخدام مساحة القرص الصلب ونسبته المئوية Comment[bs]=Aplet koji prati korištenje prostora na hard disku i postotak Comment[ca]=Una miniaplicació que controla l'ús d'espai i el tant per cent al disc dur -Comment[ca@valencia]=Una miniaplicació que controla l'ús d'espai i el tant per cent al disc dur +Comment[ca@valencia]=Una miniaplicació que controla l'ús d'espai i el tant per cent en el disc dur Comment[cs]=Aplet, jenž monitoruje zaplnění místa na disku Comment[da]=En applet som overvåger brug af harddiskplads og procent ledig plads Comment[de]=Ein Miniprogramm, das die Festplattenbelegung absolut und in Prozent überwacht Comment[el]=Μικροεφαρμογή που εποπτεύει την ποσοτική και ποσοστιαία χρήση χώρου του σκληρού δίσκου Comment[en_GB]=An applet that monitors hard disk space usage and percentage Comment[es]=Una miniaplicación que monitoriza el uso y porcentaje del espacio en disco duro Comment[et]=Kõvaketta ruumikasutust ja protsenti jälgiv aplett -Comment[eu]=Disko gogorreko lekuaren erabilera eta ehunekoa kontrolatzen dituen miniaplikazio bat +Comment[eu]=Disko zurruneko espazioaren erabilera eta ehunekoa gainbegiratzen dituen aplikaziotxo bat Comment[fi]=Näyttää levyjen tilan­käytön ja sen prosenttiosuuden Comment[fr]=Une applet surveillant l'utilisation de l'espace des disques durs et donnant des pourcentages -Comment[gl]=Un miniaplicativo que monitoriza o espazo usado do disco duro e a porcentaxe +Comment[gl]=Un trebello que vixía o espazo usado do disco duro e a porcentaxe Comment[he]=ישומון להצגת סך השימוש בכוננים השונים Comment[hu]=Egy kisalkalmazás, amely figyeli a merevlemez használatot és százalékot Comment[ia]=Un applet que monitora usage e percentage de spatio de disco dur @@ -76,7 +77,7 @@ Comment[kk]=Қатқыл дискідегі пайдаланған орын және пайызын бақылау аплеті Comment[km]=អាប់ភ្លេត​ដែល​ត្រួតពិនិត្យ​​ការ​ប្រើប្រាស់​ និង​ភាគរយ​ទំហំ​ថាសរឹង Comment[ko]=하드디스크 공간 사용량 및 비율을 보여 주는 애플릿 -Comment[lt]=Programėlė, kuri stebi disko vietos panaudojimą ir procentinį santykį +Comment[lt]=Programėlė, kuri stebi disko vietos panaudojimą ir procentinę dalį Comment[mr]=हार्ड डिस्क वापर व टक्केवारी दर्शविणारे एप्लेट Comment[nb]=Et miniprogram som overvåker bruk av plass på harddisk, og prosentvis Comment[nds]=En Lüttprogramm, dat den Fastplaat-Bruuk afsluuts un in Perzent beluert diff --git a/applets/systemmonitor/memory/Messages.sh b/applets/systemmonitor/memory/Messages.sh --- a/applets/systemmonitor/memory/Messages.sh +++ b/applets/systemmonitor/memory/Messages.sh @@ -1,2 +1,2 @@ #! /usr/bin/env bash -$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.systemmonitor.memory.pot +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` `find ../common -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.systemmonitor.memory.pot diff --git a/applets/systemmonitor/memory/metadata.desktop b/applets/systemmonitor/memory/metadata.desktop --- a/applets/systemmonitor/memory/metadata.desktop +++ b/applets/systemmonitor/memory/metadata.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Memory Status Name[ar]=حالة الذاكرة +Name[ast]=Estáu de la memoria Name[bg]=Състояние на паметта Name[bs]=Stanje memorije Name[ca]=Estat de la memòria @@ -13,7 +14,7 @@ Name[eo]=Memora stato Name[es]=Estado de la memoria Name[et]=Mälu olek -Name[eu]=Memoria-egoera +Name[eu]=Memoria egoera Name[fi]=Muistin käyttö Name[fr]=État de la mémoire Name[ga]=Stádas Cuimhne @@ -53,7 +54,6 @@ Name[sr@ijekavianlatin]=stanje memorije Name[sr@latin]=stanje memorije Name[sv]=Minnesstatus -Name[tg]=Ҳолати хотира Name[th]=สถานะหน่วยความจำ Name[tr]=Bellek Durumu Name[ug]=ئەسلەك ھالىتى @@ -77,12 +77,12 @@ Comment[eo]=RAM uzada rigardilo Comment[es]=Monitor de uso de la RAM Comment[et]=RAM-i kasutuse jälgija -Comment[eu]=RAMaen erabileraren monitorea +Comment[eu]=RAM erabileraren begirale bat Comment[fi]=Näyttää muistin käytön Comment[fr]=Surveillance de l'usage de la mémoire vive Comment[fy]=In RAM brûkme monitor Comment[ga]=Monatóir úsáid RAM -Comment[gl]=Un monitor do utilización da memoria RAM +Comment[gl]=Un vixilante do utilización da memoria RAM Comment[gu]=રેમ વપરાશ દેખરેખ Comment[he]=משמש לניטור השימוש בזיכרון ה־RAM Comment[hi]=रेम उपयोग मॉनीटर @@ -97,7 +97,7 @@ Comment[km]=កម្មវិធី​ត្រួតពិនិត្យ​ការ​ប្រើ​សតិ Comment[kn]=RAM ಬಳಕೆಯ ಮೇಲ್ವಿಚಾರಕ Comment[ko]=RAM 사용량 모니터 -Comment[lt]=RAM naudojimo stebėtojas +Comment[lt]=RAM naudojimo prižiūryklė Comment[lv]=Atmiņas izmantotāja novērotājs Comment[mai]=रैम उपयोग मानीटर Comment[mk]=Монитор на користењето на меморијата @@ -121,7 +121,7 @@ Comment[sr@ijekavianlatin]=Nadgledanje upotrebe RAM‑a Comment[sr@latin]=Nadgledanje upotrebe RAM‑a Comment[sv]=Övervakning av minnesanvändning -Comment[tg]=Монитори истифодабарии манбаъи система +Comment[tg]=Назорати истифодабарии RAM Comment[th]=ตัวติดตามการใช้งานหน่วยความจำ Comment[tr]=Bir bellek kullanımı izleyici Comment[ug]=RAM ئىشلىتىشنى كۆزەتكۈچ diff --git a/applets/systemmonitor/net/Messages.sh b/applets/systemmonitor/net/Messages.sh --- a/applets/systemmonitor/net/Messages.sh +++ b/applets/systemmonitor/net/Messages.sh @@ -1,2 +1,2 @@ #! /usr/bin/env bash -$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.systemmonitor.net.pot +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` `find ../common -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.systemmonitor.net.pot diff --git a/applets/systemmonitor/net/contents/config/config.qml b/applets/systemmonitor/net/contents/config/config.qml --- a/applets/systemmonitor/net/contents/config/config.qml +++ b/applets/systemmonitor/net/contents/config/config.qml @@ -26,4 +26,9 @@ icon: "network-workgroup" source: "netConfig.qml" } + ConfigCategory { + name: i18n("Units") + icon: "kruler" + source: "displayConfig.qml" + } } diff --git a/applets/systemmonitor/net/contents/ui/displayConfig.qml b/applets/systemmonitor/net/contents/ui/displayConfig.qml new file mode 100644 --- /dev/null +++ b/applets/systemmonitor/net/contents/ui/displayConfig.qml @@ -0,0 +1,54 @@ +/* + * Copyright 2019 George Vogiatzis + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid 2.0 +import QtQuick.Controls 2.5 as QQC2 +import org.kde.kirigami 2.5 as Kirigami + +Kirigami.FormLayout { + property int cfg_displayUnit: plasmoid.configuration.displayUnit + + QQC2.ButtonGroup { + id: displayUnitGroup + } + + QQC2.RadioButton { + id: byteDisplayUnit + QQC2.ButtonGroup.group: displayUnitGroup + + Kirigami.FormData.label: i18nc("@label", "Display unit:") + + text: i18nc("@option:radio", "Byte") + checked: cfg_displayUnit == 0 + onClicked: if (checked) {cfg_displayUnit = 0;} + } + + QQC2.RadioButton { + id: bitDisplayUnit + QQC2.ButtonGroup.group: displayUnitGroup + + text: i18nc("@option:radio", "bit") + + checked: cfg_displayUnit == 1 + onClicked: if (checked) {cfg_displayUnit = 1;} + } +} diff --git a/applets/systemmonitor/net/contents/ui/net.qml b/applets/systemmonitor/net/contents/ui/net.qml --- a/applets/systemmonitor/net/contents/ui/net.qml +++ b/applets/systemmonitor/net/contents/ui/net.qml @@ -24,6 +24,7 @@ import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kquickcontrolsaddons 2.0 as KQuickAddons +import org.kde.kcoreaddons 1.0 as KCoreAddons Applet { id: root @@ -41,14 +42,19 @@ delegate: DoublePlotter { function formatData(data) { - var value = data.value * 1024 * 8 - if (value > (1024 * 1024)) { - return i18nc("%1 is the displayed data transfer speed in megabits per second", "%1 Mbps", (value / (1024 * 1024)).toFixed(1)); + if (plasmoid.configuration.displayUnit === 0) { + var value = data.value * 1024 + return i18nc("%1 is the displayed data transfer speed in bytes per second", "%1/s", KCoreAddons.Format.formatByteSize(value)); + } else { + var value = data.value * 1024 * 8 + if (value > (1024 * 1024)) { + return i18nc("%1 is the displayed data transfer speed in megabits per second", "%1 Mbps", (value / (1024 * 1024)).toFixed(1)); + } + if (value > 1024) { + return i18nc("%1 is the displayed data transfer speed in kilobits per second", "%1 Kbps", (value / 1024)); + } + return i18nc("%1 is the displayed data transfer speed in bits per second", "%1 bps", value); } - if (value > 1024) { - return i18nc("%1 is the displayed data transfer speed in kilobits per second", "%1 Kbps", (value / 1024)); - } - return i18nc("%1 is the displayed data transfer speed in bits per second", "%1 bps", value); } } } diff --git a/applets/systemmonitor/net/metadata.desktop b/applets/systemmonitor/net/metadata.desktop --- a/applets/systemmonitor/net/metadata.desktop +++ b/applets/systemmonitor/net/metadata.desktop @@ -12,11 +12,11 @@ Name[en_GB]=Network Monitor Name[es]=Monitor de red Name[et]=Võrgujälgija -Name[eu]=Sareko monitorea +Name[eu]=Sareko begiralea Name[fi]=Verkon käyttö Name[fr]=Surveillance du réseau Name[ga]=Monatóir an Líonra -Name[gl]=Monitor da rede +Name[gl]=Vixilante da rede Name[gu]=નેટવર્ક મોનિટર Name[he]=מוניטור רשת Name[hi]=नेटवर्क नरीक्षण @@ -31,7 +31,7 @@ Name[km]=ត្រួតពិនិត្យ​បណ្ដាញ Name[kn]=ಜಾಲಬಂಧ ಮೇಲ್ವಿಚಾರಕ Name[ko]=네트워크 모니터 -Name[lt]=Tinklo stebėjimo priemonė +Name[lt]=Tinklo prižiūryklė Name[lv]=Tīkla monitors Name[mr]=संजाळ नियंत्रक Name[nb]=Nettverksovervåker @@ -53,7 +53,7 @@ Name[sr@ijekavianlatin]=nadzor mreže Name[sr@latin]=nadzor mreže Name[sv]=Nätverksövervakning -Name[tg]=Монитори шабака +Name[tg]=Назорати шабака Name[th]=ติดตามการใช้งานเครือข่าย Name[tr]=Ağ İzleyici Name[ug]=تور كۆزەتكۈچ @@ -77,12 +77,12 @@ Comment[en_GB]=A network usage monitor Comment[es]=Monitor de uso de la red Comment[et]=Võrgukasutuse jälgija -Comment[eu]=Sarearen erabileraren monitorea +Comment[eu]=Sare erabileraren begirale bat Comment[fi]=Näyttää verkon käytön Comment[fr]=Surveillance de l'usage réseau Comment[fy]=In netwurk brûkme monitor Comment[ga]=Monatóir úsáide an líonra -Comment[gl]=Un monitor do estado da rede +Comment[gl]=Un vixilante do estado da rede Comment[gu]=નેટવર્ક સ્થિતિ દેખરેખ Comment[he]=משמש לניטור השימוש ברשת Comment[hi]=नेटवर्क उपयोग मॉनीटर @@ -100,7 +100,7 @@ Comment[kn]=ಜಾಲಬಂಧ ಬಳಕೆಯ ಮೇಲ್ವಿಚಾರಕ Comment[ko]=네트워크 사용량 모니터 Comment[ku]=Temaşekerê rewşa torê -Comment[lt]=Tinklo naudojimo stebėtojas +Comment[lt]=Tinklo naudojimo prižiūryklė Comment[lv]=Tīkla noslogojuma novērotājs Comment[mk]=Монитор на користењето на мрежата Comment[ml]= ശ്രംഖല ഉപയോഗം നിരീക്ഷകന്‍ @@ -125,7 +125,7 @@ Comment[sr@latin]=Nadgledanje upotrebe mreže Comment[sv]=Övervakning av nätverksanvändning Comment[ta]=பிணைய பயன்பாடு நோட்டம் -Comment[tg]=Состояние сервера Samba +Comment[tg]=Назорати истифодабарии шабака Comment[th]=ตัวติดตามดูการใช้งานเครือข่าย Comment[tr]=Bir ağ kullanımı izleyici Comment[ug]=تور ئىشلىتىشنى كۆزەتكۈچ diff --git a/applets/systemtray/CMakeLists.txt b/applets/systemtray/CMakeLists.txt --- a/applets/systemtray/CMakeLists.txt +++ b/applets/systemtray/CMakeLists.txt @@ -3,6 +3,7 @@ plasma_install_package(package org.kde.plasma.private.systemtray) set(systemtray_SRCS + systemtraymodel.cpp systemtray.cpp ) @@ -18,11 +19,11 @@ target_link_libraries(org.kde.plasma.private.systemtray Qt5::Gui Qt5::Quick - KF5::Plasma Qt5::DBus - KF5::IconThemes + KF5::Plasma KF5::XmlGui - KF5::I18n) + KF5::I18n + KF5::ItemModels) install(TARGETS org.kde.plasma.private.systemtray DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets) diff --git a/applets/systemtray/container/package/metadata.desktop b/applets/systemtray/container/package/metadata.desktop --- a/applets/systemtray/container/package/metadata.desktop +++ b/applets/systemtray/container/package/metadata.desktop @@ -2,6 +2,7 @@ Name=System Tray Name[af]=Stelsellaai Name[ar]=صينية النظام +Name[ast]=Bandexa del sistema Name[be]=Сістэмны трэй Name[be@latin]=Systemny trej Name[bg]=Системен панел @@ -75,7 +76,6 @@ Name[sv]=Systembricka Name[ta]=சாதன தட்டு Name[te]=వ్యవస్థ ట్రె -Name[tg]=Системный лоток Name[th]=ถาดระบบ Name[tr]=Sistem Çekmecesi Name[ug]=سىستېما قوندىقى @@ -91,7 +91,7 @@ Comment[bg]=Достъп до минимизираните в системния панел програми Comment[bs]=Pristup skrivenim programima minimizovanim u sistemsku kasetu Comment[ca]=Accés a les aplicacions minimitzades ocultes a la safata del sistema -Comment[ca@valencia]=Accés a les aplicacions minimitzades ocultes a la safata del sistema +Comment[ca@valencia]=Accés a les aplicacions minimitzades ocultes en la safata del sistema Comment[cs]=Přístup ke skrytým aplikacím, které jsou minimalizované v systémové oblasti Comment[csb]=Dôwô mòżnotã taceniô ë minimalizowaniô aplikacëjów do systemòwégò zabiérnika Comment[da]=Tilgå skjulte programmer der er minimeret i statusområdet @@ -106,7 +106,7 @@ Comment[fr]=Accède aux applications cachées et réduites dans la boîte à miniatures Comment[fy]=Jou tagong ta programma's dy't yn it systeemfak rinne Comment[ga]=Rochtain feidhmchláir atá íoslaghdaithe i dtráidire an chórais -Comment[gl]=Accede a aplicativos agochados minimizados na bandexa do sistema +Comment[gl]=Accede a aplicacións agochadas minimizadas na bandexa do sistema Comment[gu]=સિસ્ટમ ટ્રેમાં નીચાં કરેલ છુપાયેલ કાર્યક્રમોને જુઓ Comment[he]=גישה ליישומים מוסתרים הממוזערים במגש המערכת Comment[hi]=तंत्र तश्तरी में न्यूनतम किए गए छुपे अनुप्रयोगों पर पहुँच @@ -122,7 +122,7 @@ Comment[km]=ចូល​ដំណើរការ​កម្មវិធី​ដែល​លាក់​ដែល​បានបង្រួម​នៅ​ក្នុង​ថាស​ប្រព័ន្ធ Comment[kn]=ವ್ಯವಸ್ಥಾ ಖಾನೆಯಲ್ಲಿ (ಟ್ರೇ) ಕನಿಷ್ಠೀಕರಿಸಲಾದ ಅಡಗಿಸಲಾದ ಅನ್ವಯಗಳನ್ನು ನಿಲುಕಿಸಿಕೋ Comment[ko]=시스템 트레이에 숨어 있는 프로그램에 접근합니다 -Comment[lt]=Prieiti prie sistemos dėkle paslėptų programų +Comment[lt]=Gauti prieigą prie į sistemos dėklą suskleistų paslėptų programų Comment[lv]=Piekļūst slēptām programmām, kas minimizētas sistēmas ikonu joslā Comment[mk]=Пристап до скриените апликации што се наоѓаат во сис. лента Comment[ml]=സിസ്റ്റം ട്രേയില്‍ മിനിമൈസ് ആയിരിക്കുന്ന ഒളിഞ്ഞിരിയ്ക്കുന്ന പ്രയോഗങ്ങളെ സമീപിയ്ക്കുക diff --git a/applets/systemtray/package/contents/ui/ConfigEntries.qml b/applets/systemtray/package/contents/ui/ConfigEntries.qml --- a/applets/systemtray/package/contents/ui/ConfigEntries.qml +++ b/applets/systemtray/package/contents/ui/ConfigEntries.qml @@ -23,8 +23,6 @@ import QtQuick.Layouts 1.3 import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents -import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 import org.kde.kquickcontrols 2.0 as KQC import org.kde.kirigami 2.5 as Kirigami @@ -38,32 +36,11 @@ property var cfg_hiddenItems: [] property alias cfg_showAllItems: showAllCheckBox.checked - function saveConfig () { - for (var i in tableView.model) { - //tableView.model[i].applet.globalShortcut = tableView.model[i].shortcut - } - } - - PlasmaCore.DataSource { - id: statusNotifierSource - engine: "statusnotifieritem" - interval: 0 - onSourceAdded: { - connectSource(source) - } - Component.onCompleted: { - connectedSources = sources - } - } - PlasmaCore.SortFilterModel { - id: statusNotifierModel - sourceModel: PlasmaCore.DataModel { - dataSource: statusNotifierSource - } + id: systemTrayModel + sourceModel: plasmoid.nativeInterface.systemTrayModel } - Kirigami.FormLayout { QQC2.CheckBox { @@ -85,29 +62,19 @@ } } - + // convert to QtObjects compatible with TableView function retrieveAllItems() { var list = []; - for (var i = 0; i < statusNotifierModel.count; ++i) { - var item = statusNotifierModel.get(i); - list.push({ - "index": i, - "taskId": item.Id, - "name": item.Title, - "iconName": item.IconName, - "icon": item.Icon - }); - } - var lastIndex = list.length; - for (var i = 0; i < plasmoid.applets.length; ++i) { - var item = plasmoid.applets[i] + for (var i = 0; i < systemTrayModel.count; i++) { + var item = systemTrayModel.get(i); + if (item.itemType === "Plasmoid" && !item.hasApplet) { + continue; + } list.push({ - "index": (i + lastIndex), - "applet": item, - "taskId": item.pluginName, - "name": item.title, - "iconName": item.icon, - "shortcut": item.globalShortcut + "taskId": item.itemId, + "name": item.display, + "icon": item.decoration, + "applet": item.applet }); } list.sort(function(a, b) { @@ -156,7 +123,7 @@ QIconItem { width: units.iconSizes.small height: width - icon: modelData.iconName || modelData.icon || "" + icon: modelData.icon } QQC2.Label { @@ -243,16 +210,12 @@ id: keySequenceItem anchors.right: parent.right - keySequence: modelData.shortcut + keySequence: modelData.applet ? modelData.applet.globalShortcut : "" // only Plasmoids have that - visible: modelData.hasOwnProperty("shortcut") + visible: modelData.hasOwnProperty("applet") onKeySequenceChanged: { - if (keySequence !== modelData.shortcut) { - // both SNIs and plasmoids are listed in the same TableView - // but they come from two separate models, so we need to subtract - // the SNI model count to get the actual plasmoid index - var index = modelData.index - statusNotifierModel.count - plasmoid.applets[index].globalShortcut = keySequence + if (modelData.applet && keySequence !== modelData.applet.globalShortcut) { + modelData.applet.globalShortcut = keySequence iconsPage.configurationChanged() } diff --git a/applets/systemtray/package/contents/ui/ConfigGeneral.qml b/applets/systemtray/package/contents/ui/ConfigGeneral.qml --- a/applets/systemtray/package/contents/ui/ConfigGeneral.qml +++ b/applets/systemtray/package/contents/ui/ConfigGeneral.qml @@ -21,8 +21,6 @@ import QtQuick.Controls 2.3 as QtControls import QtQuick.Layouts 1.0 as QtLayouts -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kquickcontrolsaddons 2.0 import org.kde.kirigami 2.5 as Kirigami @@ -91,13 +89,13 @@ model: plasmoid.nativeInterface.availablePlasmoids delegate: QtControls.CheckBox { QtLayouts.Layout.minimumWidth: childrenRect.width - checked: cfg_extraItems.indexOf(plugin) != -1 + checked: cfg_extraItems.indexOf(itemId) != -1 implicitWidth: itemLayout.width + itemLayout.x onCheckedChanged: { - var index = cfg_extraItems.indexOf(plugin); + var index = cfg_extraItems.indexOf(itemId); if (checked) { if (index === -1) { - cfg_extraItems.push(plugin); + cfg_extraItems.push(itemId); } } else { if (index > -1) { @@ -112,7 +110,7 @@ x: dummyCheckbox.width QIconItem { icon: model.decoration - width: units.iconSizes.small + width: Kirigami.Units.iconSizes.small height: width } QtControls.Label { diff --git a/applets/systemtray/package/contents/ui/ExpandedRepresentation.qml b/applets/systemtray/package/contents/ui/ExpandedRepresentation.qml --- a/applets/systemtray/package/contents/ui/ExpandedRepresentation.qml +++ b/applets/systemtray/package/contents/ui/ExpandedRepresentation.qml @@ -17,128 +17,127 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import QtQuick 2.1 -import QtQuick.Layouts 1.1 +import QtQuick 2.12 +import QtQuick.Layouts 1.12 + import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras -Item { +ColumnLayout { id: expandedRepresentation - //set width/height to avoid an useless Dialog resize width: Layout.minimumWidth height: Layout.minimumHeight Layout.minimumWidth: units.gridUnit * 24 Layout.minimumHeight: units.gridUnit * 21 Layout.preferredWidth: Layout.minimumWidth - Layout.preferredHeight: Layout.minimumHeight * 1.5 + Layout.preferredHeight: Layout.minimumHeight + Layout.maximumWidth: Layout.minimumWidth + Layout.maximumHeight: Layout.minimumHeight + spacing: 0 // avoid gap between title and content property alias activeApplet: container.activeApplet property alias hiddenLayout: hiddenItemsView.layout - PlasmaComponents.ToolButton { - id: pinButton - anchors.right: parent.right - width: Math.round(units.gridUnit * 1.25) - height: width - checkable: true - checked: plasmoid.configuration.pin - onCheckedChanged: plasmoid.configuration.pin = checked - iconSource: "window-pin" - z: 2 - tooltip: i18n("Keep Open") - } + RowLayout { - PlasmaExtras.Heading { - id: heading - level: 1 + PlasmaExtras.Heading { + id: heading + Layout.fillWidth: true + level: 1 + Layout.leftMargin: { + //Menu mode + if (!activeApplet && hiddenItemsView.visible && !LayoutMirroring.enabled) { + return units.smallSpacing; - anchors { - left: parent.left - top: parent.top - right: parent.right - topMargin: hiddenItemsView.visible ? units.smallSpacing : 0 - leftMargin: { + //applet open, sidebar + } else if (activeApplet && hiddenItemsView.visible && !LayoutMirroring.enabled) { + return hiddenItemsView.width + units.largeSpacing; + + //applet open, no sidebar + } else { + return 0; + } + } + Layout.rightMargin: { //Menu mode - if (!activeApplet && hiddenItemsView.visible) { + if (!activeApplet && hiddenItemsView.visible && LayoutMirroring.enabled) { return units.smallSpacing; //applet open, sidebar - } else if (activeApplet && hiddenItemsView.visible) { - return hiddenItemsView.iconColumnWidth + units.largeSpacing; + } else if (activeApplet && hiddenItemsView.visible && LayoutMirroring.enabled) { + return hiddenItemsView.width + units.largeSpacing; //applet open, no sidebar } else { return 0; } } - } - visible: activeApplet - text: activeApplet ? activeApplet.title : "" - MouseArea { - anchors.fill: parent - onClicked: { - if (activeApplet) { - activeApplet.expanded = false; - dialog.visible = true; + visible: activeApplet + text: activeApplet ? activeApplet.title : "" + MouseArea { + anchors.fill: parent + onClicked: { + if (activeApplet) { + activeApplet.expanded = false; + dialog.visible = true; + } } } } - } - PlasmaExtras.Heading { - id: noAppletHeading - level: 1 - anchors { - left: parent.left - top: parent.top - right: parent.right - topMargin: hiddenItemsView.visible ? units.smallSpacing : 0 - leftMargin: units.smallSpacing - } - text: i18n("Status and Notifications") - visible: !heading.visible - } - PlasmaCore.SvgItem { - anchors { - left: parent.left - leftMargin: hiddenLayout.width - top: parent.top - bottom: parent.bottom - margins: -units.gridUnit + PlasmaExtras.Heading { + id: noAppletHeading + visible: !activeApplet + Layout.fillWidth: true + level: 1 + text: i18n("Status and Notifications") } - visible: hiddenItemsView.visible && activeApplet - width: lineSvg.elementSize("vertical-line").width + PlasmaComponents.ToolButton { + id: pinButton + Layout.preferredHeight: Math.round(units.gridUnit * 1.25) + Layout.preferredWidth: Layout.preferredHeight + checkable: true + checked: plasmoid.configuration.pin + onToggled: plasmoid.configuration.pin = checked + icon.name: "window-pin" + PlasmaComponents.ToolTip { + text: i18n("Keep Open") + } + } + } - elementId: "vertical-line" + RowLayout { + spacing: 0 // must be 0 so that the separator is as close to the indicator as possible - svg: PlasmaCore.Svg { - id: lineSvg; - imagePath: "widgets/line" + HiddenItemsView { + id: hiddenItemsView + Layout.fillWidth: !activeApplet + Layout.preferredWidth: activeApplet ? iconColumnWidth : -1 + Layout.fillHeight: true } - } - HiddenItemsView { - id: hiddenItemsView - anchors { - left: parent.left - top: noAppletHeading.bottom - topMargin: units.smallSpacing - bottom: parent.bottom + PlasmaCore.SvgItem { + visible: hiddenItemsView.visible && activeApplet + Layout.fillHeight: true + Layout.preferredWidth: lineSvg.elementSize("vertical-line").width + elementId: "vertical-line" + svg: PlasmaCore.Svg { + id: lineSvg; + imagePath: "widgets/line" + } } - } - PlasmoidPopupsContainer { - id: container - anchors { - left: parent.left - right: parent.right - top: heading.bottom - bottom: parent.bottom - leftMargin: hiddenItemsView.visible ? hiddenItemsView.iconColumnWidth + units.largeSpacing : 0 + PlasmoidPopupsContainer { + id: container + visible: activeApplet + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: hiddenItemsView.visible && activeApplet && !LayoutMirroring.enabled ? units.largeSpacing : 0 + Layout.rightMargin: hiddenItemsView.visible && activeApplet && LayoutMirroring.enabled ? units.largeSpacing : 0 } } } diff --git a/applets/systemtray/package/contents/ui/ExpanderArrow.qml b/applets/systemtray/package/contents/ui/ExpanderArrow.qml --- a/applets/systemtray/package/contents/ui/ExpanderArrow.qml +++ b/applets/systemtray/package/contents/ui/ExpanderArrow.qml @@ -21,9 +21,6 @@ import QtQuick 2.0 import QtQuick.Layouts 1.1 import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents -import org.kde.plasma.extras 2.0 as PlasmaExtras - PlasmaCore.ToolTipArea { id: tooltip diff --git a/applets/systemtray/package/contents/ui/PlasmoidPopupsContainer.qml b/applets/systemtray/package/contents/ui/PlasmoidPopupsContainer.qml --- a/applets/systemtray/package/contents/ui/PlasmoidPopupsContainer.qml +++ b/applets/systemtray/package/contents/ui/PlasmoidPopupsContainer.qml @@ -20,8 +20,8 @@ import QtQuick 2.4 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.4 +//needed for units import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents StackView { id: mainStack diff --git a/applets/systemtray/package/contents/ui/items/AbstractItem.qml b/applets/systemtray/package/contents/ui/items/AbstractItem.qml --- a/applets/systemtray/package/contents/ui/items/AbstractItem.qml +++ b/applets/systemtray/package/contents/ui/items/AbstractItem.qml @@ -17,7 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import QtQuick 2.1 +import QtQuick 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents @@ -40,6 +40,7 @@ property QtObject model signal clicked(var mouse) + signal pressed(var mouse) signal wheel(var wheel) signal contextMenu(var mouse) @@ -77,19 +78,21 @@ //BEGIN CONNECTIONS - onEffectiveStatusChanged: updateItemVisibility(abstractItem); + property int creationId // used for item order tie breaking + onEffectiveStatusChanged: updateItemVisibility(abstractItem) + onCategoryChanged: updateItemVisibility(abstractItem) + onTextChanged: updateItemVisibility(abstractItem) + Component.onCompleted: { + creationId = root.creationIdCounter++ + updateItemVisibility(abstractItem) + } onContainsMouseChanged: { if (hidden && containsMouse) { root.hiddenLayout.hoveredItem = abstractItem } } - Component.onCompleted: updateItemVisibility(abstractItem); - - //dangerous but needed due how repeater reparents - onParentChanged: updateItemVisibility(abstractItem); - //END CONNECTIONS PulseAnimation { @@ -99,18 +102,40 @@ units.longDuration > 0 } + function activated() { + activatedAnimation.start() + } + + SequentialAnimation { + id: activatedAnimation + loops: 1 + + ScaleAnimator { + target: iconItem + from: 1 + to: 0.5 + duration: units.shortDuration + easing.type: Easing.InQuad + } + + ScaleAnimator { + target: iconItem + from: 0.5 + to: 1 + duration: units.shortDuration + easing.type: Easing.OutQuad + } + } + MouseArea { - id: mouseArea anchors.fill: abstractItem hoverEnabled: true drag.filterChildren: true acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton onClicked: abstractItem.clicked(mouse) onPressed: { abstractItem.hideToolTip() - if (mouse.button === Qt.RightButton) { - abstractItem.contextMenu(mouse) - } + abstractItem.pressed(mouse) } onPressAndHold: { abstractItem.contextMenu(mouse) diff --git a/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml b/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml --- a/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml +++ b/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml @@ -19,7 +19,6 @@ import QtQuick 2.1 import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents AbstractItem { id: plasmoidContainer @@ -43,6 +42,11 @@ applet.expanded = true; } } + onPressed: { + if (mouse.button === Qt.RightButton) { + plasmoidContainer.contextMenu(mouse); + } + } onContextMenu: { if (applet) { plasmoid.nativeInterface.showPlasmoidMenu(applet, 0, plasmoidContainer.hidden ? applet.height : 0); @@ -62,14 +66,17 @@ } Connections { target: applet + onActivated: plasmoidContainer.activated() + onExpandedChanged: { if (expanded) { var oldApplet = root.activeApplet; root.activeApplet = applet; if (oldApplet) { oldApplet.expanded = false; } dialog.visible = true; + plasmoidContainer.activated() } else if (root.activeApplet === applet) { if (!applet.parent.hidden) { diff --git a/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml b/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml --- a/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml +++ b/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml @@ -19,7 +19,6 @@ import QtQuick 2.1 import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents AbstractItem { id: taskIcon @@ -48,7 +47,18 @@ PlasmaCore.IconItem { id: iconItem - source: Icon ? Icon : IconName + source: { + if (taskIcon.status === PlasmaCore.Types.NeedsAttentionStatus) { + if (AttentionIcon) { + return AttentionIcon + } + if (AttentionIconName) { + return AttentionIconName + } + } + return Icon ? Icon : IconName + } + width: Math.min(parent.width, parent.height) height: width active: taskIcon.containsMouse @@ -80,6 +90,7 @@ openContextMenu(pos); } }); + taskIcon.activated() break; } case Qt.RightButton: @@ -93,6 +104,7 @@ operation.y = pos.y; service.startOperationCall(operation); + taskIcon.activated() break; } } diff --git a/applets/systemtray/package/contents/ui/main.qml b/applets/systemtray/package/contents/ui/main.qml --- a/applets/systemtray/package/contents/ui/main.qml +++ b/applets/systemtray/package/contents/ui/main.qml @@ -55,45 +55,84 @@ // workaround https://bugreports.qt.io/browse/QTBUG-71238 / https://bugreports.qt.io/browse/QTBUG-72004 property Component plasmoidItemComponent: Qt.createComponent("items/PlasmoidItem.qml") + property int creationIdCounter: 0 + Plasmoid.onExpandedChanged: { if (!plasmoid.expanded) { dialog.visible = plasmoid.expanded; } } + // temporary hack to fix known broken categories + // should go away as soon as fixes are merged + readonly property var categoryOverride: { + "org.kde.discovernotifier": "SystemServices", + "org.kde.plasma.networkmanagement": "Hardware", + "org.kde.kdeconnect": "Hardware", + "org.kde.plasma.keyboardindicator": "Hardware", + "touchpad": "Hardware" + } + + readonly property var categoryOrder: [ + "UnknownCategory", "ApplicationStatus", "Communications", + "SystemServices", "Hardware" + ] + function indexForItemCategory(item) { + if (item.itemId == "org.kde.plasma.notifications") { + return -1 + } + var i = categoryOrder.indexOf(categoryOverride[item.itemId] || item.category) + return i == -1 ? categoryOrder.indexOf("UnknownCategory") : i + } + + // return negative integer if a < b, 0 if a === b, and positive otherwise + function compareItems(a, b) { + var categoryDiff = indexForItemCategory(a) - indexForItemCategory(b) + var textDiff = (categoryDiff != 0 ? categoryDiff : a.text.localeCompare(b.text)) + return textDiff != 0 ? textDiff : b.creationId - a.creationId + } + + function moveItemAt(item, container, index) { + if (container.children.length == 0) { + item.parent = container + } else { + if (index == container.children.length) { + var other = container.children[index - 1] + if (item != other) { + plasmoid.nativeInterface.reorderItemAfter(item, other) + } + } else { + var other = container.children[index] + if (item != other) { + plasmoid.nativeInterface.reorderItemBefore(item, other) + } + } + } + } + + function reorderItem(item, container) { + var i = 0; + while (i < container.children.length && + compareItems(container.children[i], item) <= 0) { + i++ + } + moveItemAt(item, container, i) + } + function updateItemVisibility(item) { switch (item.effectiveStatus) { case PlasmaCore.Types.HiddenStatus: - if (item.parent === invisibleEntriesContainer) { - return; + if (item.parent != invisibleEntriesContainer) { + item.parent = invisibleEntriesContainer; } - - item.parent = invisibleEntriesContainer; break; case PlasmaCore.Types.ActiveStatus: - if (visibleLayout.children.length === 0) { - item.parent = visibleLayout; - //notifications is always the first - } else if (visibleLayout.children[0].itemId === "org.kde.plasma.notifications" && - item.itemId !== "org.kde.plasma.notifications") { - plasmoid.nativeInterface.reorderItemAfter(item, visibleLayout.children[0]); - } else if (visibleLayout.children[0] !== item) { - plasmoid.nativeInterface.reorderItemBefore(item, visibleLayout.children[0]); - } + reorderItem(item, visibleLayout) break; case PlasmaCore.Types.PassiveStatus: - - if (hiddenLayout.children.length === 0) { - item.parent = hiddenLayout; - //notifications is always the first - } else if (hiddenLayout.children[0].itemId === "org.kde.plasma.notifications" && - item.itemId !== "org.kde.plasma.notifications") { - plasmoid.nativeInterface.reorderItemAfter(item, hiddenLayout.children[0]); - } else if (hiddenLayout.children[0] !== item) { - plasmoid.nativeInterface.reorderItemBefore(item, hiddenLayout.children[0]); - } + reorderItem(item, hiddenLayout) item.x = 0; break; } diff --git a/applets/systemtray/package/metadata.desktop b/applets/systemtray/package/metadata.desktop --- a/applets/systemtray/package/metadata.desktop +++ b/applets/systemtray/package/metadata.desktop @@ -2,6 +2,7 @@ Name=System Tray Name[af]=Stelsellaai Name[ar]=صينية النظام +Name[ast]=Bandexa del sistema Name[be]=Сістэмны трэй Name[be@latin]=Systemny trej Name[bg]=Системен панел @@ -75,7 +76,6 @@ Name[sv]=Systembricka Name[ta]=சாதன தட்டு Name[te]=వ్యవస్థ ట్రె -Name[tg]=Системный лоток Name[th]=ถาดระบบ Name[tr]=Sistem Çekmecesi Name[ug]=سىستېما قوندىقى diff --git a/applets/systemtray/systemtray.h b/applets/systemtray/systemtray.h --- a/applets/systemtray/systemtray.h +++ b/applets/systemtray/systemtray.h @@ -29,11 +29,17 @@ class QDBusPendingCallWatcher; class QDBusConnection; class QQuickItem; +namespace Plasma { + class Service; +} class PlasmoidModel; +class StatusNotifierModel; +class SystemTrayModel; class SystemTray : public Plasma::Containment { Q_OBJECT + Q_PROPERTY(QAbstractItemModel* systemTrayModel READ systemTrayModel CONSTANT) Q_PROPERTY(QAbstractItemModel* availablePlasmoids READ availablePlasmoids CONSTANT) Q_PROPERTY(QStringList allowedPlasmoids READ allowedPlasmoids WRITE setAllowedPlasmoids NOTIFY allowedPlasmoidsChanged) Q_PROPERTY(QStringList defaultPlasmoids READ defaultPlasmoids CONSTANT) @@ -47,6 +53,8 @@ void restoreContents(KConfigGroup &group) override; void restorePlasmoids(); + QAbstractItemModel* systemTrayModel(); + QStringList defaultPlasmoids() const; QAbstractItemModel* availablePlasmoids(); @@ -115,11 +123,13 @@ private: void initDBusActivatables(); QStringList m_defaultPlasmoids; - QHash m_systrayApplets; + QHash m_systrayApplets; QHash m_dbusActivatableTasks; QStringList m_allowedPlasmoids; PlasmoidModel *m_availablePlasmoidsModel; + StatusNotifierModel *m_statusNotifierModel; + SystemTrayModel *m_systemTrayModel; QHash m_knownPlugins; QHash m_dbusServiceCounts; }; diff --git a/applets/systemtray/systemtray.cpp b/applets/systemtray/systemtray.cpp --- a/applets/systemtray/systemtray.cpp +++ b/applets/systemtray/systemtray.cpp @@ -18,6 +18,7 @@ ***************************************************************************/ #include "systemtray.h" +#include "systemtraymodel.h" #include "debug.h" #include @@ -42,27 +43,21 @@ #include -class PlasmoidModel: public QStandardItemModel -{ -public: - explicit PlasmoidModel(QObject *parent = nullptr) - : QStandardItemModel(parent) - { - } - - QHash roleNames() const override { - QHash roles = QStandardItemModel::roleNames(); - roles[Qt::UserRole+1] = "plugin"; - return roles; - } -}; - SystemTray::SystemTray(QObject *parent, const QVariantList &args) : Plasma::Containment(parent, args), - m_availablePlasmoidsModel(nullptr) + m_availablePlasmoidsModel(nullptr), + m_systemTrayModel(new SystemTrayModel(this)) { setHasConfigurationInterface(true); setContainmentType(Plasma::Types::CustomEmbeddedContainment); + + PlasmoidModel *currentPlasmoidsModel = new PlasmoidModel(m_systemTrayModel); + connect(this, &SystemTray::appletAdded, currentPlasmoidsModel, &PlasmoidModel::addApplet); + connect(this, &SystemTray::appletRemoved, currentPlasmoidsModel, &PlasmoidModel::removeApplet); + m_systemTrayModel->addSourceModel(currentPlasmoidsModel); + + m_statusNotifierModel = new StatusNotifierModel(m_systemTrayModel); + m_systemTrayModel->addSourceModel(m_statusNotifierModel); } SystemTray::~SystemTray() @@ -77,7 +72,7 @@ if (!info.isValid() || info.value(QStringLiteral("X-Plasma-NotificationArea")) != "true") { continue; } - m_systrayApplets[info.pluginId()] = KPluginInfo(info); + m_systrayApplets[info.pluginId()] = info; if (info.isEnabledByDefault()) { m_defaultPlasmoids += info.pluginId(); @@ -396,21 +391,21 @@ QStringList ownApplets; - QMap sortedApplets; + QMap sortedApplets; for (auto it = m_systrayApplets.constBegin(); it != m_systrayApplets.constEnd(); ++it) { - const KPluginInfo &info = it.value(); - if (m_allowedPlasmoids.contains(info.pluginName()) && ! - m_dbusActivatableTasks.contains(info.pluginName())) { + const KPluginMetaData &info = it.value(); + if (m_allowedPlasmoids.contains(info.pluginId()) && ! + m_dbusActivatableTasks.contains(info.pluginId())) { //FIXME // if we already have a plugin with this exact name in it, then check if it is the // same plugin and skip it if it is indeed already listed if (sortedApplets.contains(info.name())) { bool dupe = false; // it is possible (though poor form) to have multiple applets // with the same visible name but different plugins, so we hve to check all values const auto infos = sortedApplets.values(info.name()); - for (const KPluginInfo &existingInfo : infos) { - if (existingInfo.pluginName() == info.pluginName()) { + for (const KPluginMetaData &existingInfo : infos) { + if (existingInfo.pluginId() == info.pluginId()) { dupe = true; break; } @@ -427,16 +422,21 @@ } } - for (const KPluginInfo &info : qAsConst(sortedApplets)) { + for (const KPluginMetaData &info : qAsConst(sortedApplets)) { qCDebug(SYSTEM_TRAY) << " Adding applet: " << info.name(); - if (m_allowedPlasmoids.contains(info.pluginName())) { - newTask(info.pluginName()); + if (m_allowedPlasmoids.contains(info.pluginId())) { + newTask(info.pluginId()); } } initDBusActivatables(); } +QAbstractItemModel *SystemTray::systemTrayModel() +{ + return m_systemTrayModel; +} + QStringList SystemTray::defaultPlasmoids() const { return m_defaultPlasmoids; @@ -446,19 +446,6 @@ { if (!m_availablePlasmoidsModel) { m_availablePlasmoidsModel = new PlasmoidModel(this); - - for (const KPluginInfo &info : qAsConst(m_systrayApplets)) { - QString name = info.name(); - const QString dbusactivation = info.property(QStringLiteral("X-Plasma-DBusActivationService")).toString(); - - if (!dbusactivation.isEmpty()) { - name += i18n(" (Automatic load)"); - } - QStandardItem *item = new QStandardItem(QIcon::fromTheme(info.icon()), name); - item->setData(info.pluginName()); - m_availablePlasmoidsModel->appendRow(item); - } - m_availablePlasmoidsModel->sort(0 /*column*/); } return m_availablePlasmoidsModel; } diff --git a/applets/systemtray/systemtraymodel.h b/applets/systemtray/systemtraymodel.h new file mode 100644 --- /dev/null +++ b/applets/systemtray/systemtraymodel.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2019 Konrad Materka * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef SYSTEMTRAYMODEL_H +#define SYSTEMTRAYMODEL_H + +#include + +#include +#include +#include + +namespace Plasma { + class Applet; +} + +enum class BaseRole { + ItemType = Qt::UserRole + 1, + ItemId, + CanRender, + LastBaseRole +}; + +class PlasmoidModel: public QStandardItemModel +{ + Q_OBJECT +public: + enum class Role { + Applet = static_cast(BaseRole::LastBaseRole) + 1, + HasApplet + }; + + explicit PlasmoidModel(QObject *parent = nullptr); + + QHash roleNames() const override; + +public slots: + void addApplet(Plasma::Applet *applet); + void removeApplet(Plasma::Applet *applet); +}; + +class StatusNotifierModel : public QStandardItemModel, public Plasma::DataEngineConsumer { + Q_OBJECT +public: + enum class Role { + DataEngineSource = static_cast(BaseRole::LastBaseRole) + 100, + AttentionIcon, + AttentionIconName, + AttentionMovieName, + Category, + Icon, + IconName, + IconThemePath, + IconsChanged, + Id, + ItemIsMenu, + OverlayIconName, + Status, + StatusChanged, + Title, + TitleChanged, + ToolTipChanged, + ToolTipIcon, + ToolTipSubTitle, + ToolTipTitle, + WindowId + }; + + StatusNotifierModel(QObject* parent); + + QHash roleNames() const override; + + Plasma::Service *serviceForSource(const QString &source); + +public slots: + void addSource(const QString &source); + void removeSource(const QString &source); + void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data); + +private: + void updateItemData(QStandardItem *dataItem, const Plasma::DataEngine::Data &data, const Role role); + + Plasma::DataEngine *m_dataEngine = nullptr; + QStringList m_sources; + QHash m_services; +}; + +class SystemTrayModel : public KConcatenateRowsProxyModel +{ + Q_OBJECT +public: + explicit SystemTrayModel(QObject *parent = nullptr); + + QHash roleNames() const override; + + void addSourceModel(QAbstractItemModel *sourceModel); + +private: + QHash m_roleNames; +}; + +#endif // SYSTEMTRAYMODEL_H diff --git a/applets/systemtray/systemtraymodel.cpp b/applets/systemtray/systemtraymodel.cpp new file mode 100644 --- /dev/null +++ b/applets/systemtray/systemtraymodel.cpp @@ -0,0 +1,255 @@ +/*************************************************************************** + * Copyright (C) 2019 Konrad Materka * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "systemtraymodel.h" +#include "debug.h" + +#include + +#include +#include +#include +#include + +#include + +PlasmoidModel::PlasmoidModel(QObject *parent) : QStandardItemModel(parent) +{ + for (const auto &info : Plasma::PluginLoader::self()->listAppletMetaData(QString())) { + if (!info.isValid() || info.value(QStringLiteral("X-Plasma-NotificationArea")) != "true") { + continue; + } + + QString name = info.name(); + const QString dbusactivation = + info.rawData().value(QStringLiteral("X-Plasma-DBusActivationService")).toString(); + + if (!dbusactivation.isEmpty()) { + name += i18n(" (Automatic load)"); + } + QStandardItem *item = new QStandardItem(QIcon::fromTheme(info.iconName()), name); + item->setData(info.pluginId(), static_cast(BaseRole::ItemId)); + item->setData("Plasmoid", static_cast(BaseRole::ItemType)); + item->setData(false, static_cast(BaseRole::CanRender)); + item->setData(false, static_cast(Role::HasApplet)); + appendRow(item); + } + sort(0); +} + +QHash PlasmoidModel::roleNames() const +{ + QHash roles = QStandardItemModel::roleNames(); + + roles.insert(static_cast(BaseRole::ItemType), QByteArrayLiteral("itemType")); + roles.insert(static_cast(BaseRole::ItemId), QByteArrayLiteral("itemId")); + roles.insert(static_cast(BaseRole::CanRender), QByteArrayLiteral("canRender")); + + roles.insert(static_cast(Role::Applet), QByteArrayLiteral("applet")); + roles.insert(static_cast(Role::HasApplet), QByteArrayLiteral("hasApplet")); + + return roles; +} + +void PlasmoidModel::addApplet(Plasma::Applet *applet) +{ + auto info = applet->pluginMetaData(); + QStandardItem *dataItem = nullptr; + for (int i = 0; i < rowCount(); i++) { + QStandardItem *currentItem = item(i); + if (currentItem->data(static_cast(BaseRole::ItemId)) == info.pluginId()) { + dataItem = currentItem; + break; + } + } + + if (!dataItem) { + dataItem = new QStandardItem(QIcon::fromTheme(info.iconName()), info.name()); + appendRow(dataItem); + } + + dataItem->setData(info.pluginId(), static_cast(BaseRole::ItemId)); + dataItem->setData(applet->property("_plasma_graphicObject"), static_cast(Role::Applet)); + dataItem->setData(true, static_cast(Role::HasApplet)); + dataItem->setData(true, static_cast(BaseRole::CanRender)); +} + +void PlasmoidModel::removeApplet(Plasma::Applet *applet) +{ + int rows = rowCount(); + for (int i = 0; i < rows; i++) { + QStandardItem *currentItem = item(i); + QVariant plugin = currentItem->data(static_cast(BaseRole::ItemId)); + if (plugin.isValid() && plugin.value() == applet->pluginMetaData().pluginId()) { + currentItem->setData(false, static_cast(BaseRole::CanRender)); + currentItem->setData(QVariant(), static_cast(Role::Applet)); + currentItem->setData(false, static_cast(Role::HasApplet)); + return; + } + } +} + +StatusNotifierModel::StatusNotifierModel(QObject *parent) : QStandardItemModel(parent) +{ + m_dataEngine = dataEngine(QStringLiteral("statusnotifieritem")); + + connect(m_dataEngine, &Plasma::DataEngine::sourceAdded, this, &StatusNotifierModel::addSource); + connect(m_dataEngine, &Plasma::DataEngine::sourceRemoved, this, &StatusNotifierModel::removeSource); + + m_dataEngine->connectAllSources(this); +} + +QHash StatusNotifierModel::roleNames() const +{ + QHash roles = QStandardItemModel::roleNames(); + + roles.insert(static_cast(BaseRole::ItemType), QByteArrayLiteral("itemType")); + roles.insert(static_cast(BaseRole::ItemId), QByteArrayLiteral("itemId")); + roles.insert(static_cast(BaseRole::CanRender), QByteArrayLiteral("canRender")); + + roles.insert(static_cast(Role::DataEngineSource), QByteArrayLiteral("DataEngineSource")); + roles.insert(static_cast(Role::AttentionIcon), QByteArrayLiteral("AttentionIcon")); + roles.insert(static_cast(Role::AttentionIconName), QByteArrayLiteral("AttentionIconName")); + roles.insert(static_cast(Role::AttentionMovieName), QByteArrayLiteral("AttentionMovieName")); + roles.insert(static_cast(Role::Category), QByteArrayLiteral("Category")); + roles.insert(static_cast(Role::Icon), QByteArrayLiteral("Icon")); + roles.insert(static_cast(Role::IconName), QByteArrayLiteral("IconName")); + roles.insert(static_cast(Role::IconThemePath), QByteArrayLiteral("IconThemePath")); + roles.insert(static_cast(Role::IconsChanged), QByteArrayLiteral("IconsChanged")); + roles.insert(static_cast(Role::Id), QByteArrayLiteral("Id")); + roles.insert(static_cast(Role::ItemIsMenu), QByteArrayLiteral("ItemIsMenu")); + roles.insert(static_cast(Role::OverlayIconName), QByteArrayLiteral("OverlayIconName")); + roles.insert(static_cast(Role::Status), QByteArrayLiteral("Status")); + roles.insert(static_cast(Role::StatusChanged), QByteArrayLiteral("StatusChanged")); + roles.insert(static_cast(Role::Title), QByteArrayLiteral("Title")); + roles.insert(static_cast(Role::TitleChanged), QByteArrayLiteral("TitleChanged")); + roles.insert(static_cast(Role::ToolTipChanged), QByteArrayLiteral("ToolTipChanged")); + roles.insert(static_cast(Role::ToolTipIcon), QByteArrayLiteral("ToolTipIcon")); + roles.insert(static_cast(Role::ToolTipSubTitle), QByteArrayLiteral("ToolTipSubTitle")); + roles.insert(static_cast(Role::ToolTipTitle), QByteArrayLiteral("ToolTipTitle")); + roles.insert(static_cast(Role::WindowId), QByteArrayLiteral("WindowId")); + + return roles; +} + +Plasma::Service *StatusNotifierModel::serviceForSource(const QString &source) +{ + if (m_services.contains(source)) { + return m_services.value(source); + } + + Plasma::Service *service = m_dataEngine->serviceForSource(source); + if (!service) { + return nullptr; + } + m_services[source] = service; + return service; +} + +void StatusNotifierModel::addSource(const QString &source) +{ + m_dataEngine->connectSource(source, this); +} + +void StatusNotifierModel::removeSource(const QString &source) +{ + m_dataEngine->disconnectSource(source, this); + if (m_sources.contains(source)) { + removeRow(m_sources.indexOf(source)); + m_sources.removeAll(source); + } + + QHash::iterator it = m_services.find(source); + if (it != m_services.end()) { + delete it.value(); + m_services.erase(it); + } +} + +void StatusNotifierModel::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) +{ + QStandardItem *dataItem; + if (m_sources.contains(sourceName)) { + dataItem = item(m_sources.indexOf(sourceName)); + } else { + dataItem = new QStandardItem(); + dataItem->setData("StatusNotifier", static_cast(BaseRole::ItemType)); + dataItem->setData(true, static_cast(BaseRole::CanRender)); + } + + dataItem->setData(data.value("Title"), Qt::DisplayRole); + QVariant icon = data.value("Icon"); + if (icon.isValid() && icon.canConvert() && !icon.value().isNull()) { + dataItem->setData(icon, Qt::DecorationRole); + } else { + dataItem->setData(data.value("IconName"), Qt::DecorationRole); + } + + dataItem->setData(data.value("Id"), static_cast(BaseRole::ItemId)); + + dataItem->setData(sourceName, static_cast(Role::DataEngineSource)); + updateItemData(dataItem, data, Role::AttentionIcon); + updateItemData(dataItem, data, Role::AttentionIconName); + updateItemData(dataItem, data, Role::AttentionMovieName); + updateItemData(dataItem, data, Role::Category); + updateItemData(dataItem, data, Role::Icon); + updateItemData(dataItem, data, Role::IconName); + updateItemData(dataItem, data, Role::IconThemePath); + updateItemData(dataItem, data, Role::IconsChanged); + updateItemData(dataItem, data, Role::Id); + updateItemData(dataItem, data, Role::ItemIsMenu); + updateItemData(dataItem, data, Role::OverlayIconName); + updateItemData(dataItem, data, Role::Status); + updateItemData(dataItem, data, Role::StatusChanged); + updateItemData(dataItem, data, Role::Title); + updateItemData(dataItem, data, Role::TitleChanged); + updateItemData(dataItem, data, Role::ToolTipChanged); + updateItemData(dataItem, data, Role::ToolTipIcon); + updateItemData(dataItem, data, Role::ToolTipSubTitle); + updateItemData(dataItem, data, Role::ToolTipTitle); + updateItemData(dataItem, data, Role::WindowId); + + if (!m_sources.contains(sourceName)) { + m_sources.append(sourceName); + appendRow(dataItem); + } +} + +void StatusNotifierModel::updateItemData(QStandardItem *dataItem, + const Plasma::DataEngine::Data &data, const Role role) +{ + int roleId = static_cast(role); + dataItem->setData(data.value(roleNames().value(roleId)), roleId); +} + +SystemTrayModel::SystemTrayModel(QObject *parent) : KConcatenateRowsProxyModel(parent) +{ + m_roleNames = KConcatenateRowsProxyModel::roleNames(); +} + +QHash SystemTrayModel::roleNames() const +{ + return m_roleNames; +} + +void SystemTrayModel::addSourceModel(QAbstractItemModel *sourceModel) +{ + m_roleNames.unite(sourceModel->roleNames()); + KConcatenateRowsProxyModel::addSourceModel(sourceModel); +} diff --git a/appmenu/appmenu.desktop b/appmenu/appmenu.desktop --- a/appmenu/appmenu.desktop +++ b/appmenu/appmenu.desktop @@ -15,7 +15,7 @@ Name[eu]=Aplikazio-menuen daimona Name[fi]=Sovellusvalikkopalvelu Name[fr]=Démon de menus des applications -Name[gl]=Servizo de menús do aplicativo +Name[gl]=Servizo de menús da aplicación Name[he]=תהליך רקע של תפריט היישומים Name[hu]=Alkalmazás menük démon Name[ia]=Demone de menus de application @@ -44,6 +44,7 @@ Name[sr@ijekavianlatin]=Demon programskih menija Name[sr@latin]=Demon programskih menija Name[sv]=Demon för programmenyer +Name[tg]=Раванди дохилии феҳристҳои барнома Name[tr]=Uygulama menü araçları Name[uk]=Фонова служба меню програм Name[x-test]=xxApplication menus daemonxx @@ -64,7 +65,7 @@ Comment[eu]=Aplikazioen menua mahaigainera transferitzen du Comment[fi]=Siirtää sovelluksen valikon työpöydälle Comment[fr]=Transfère le menu des applications sur le bureau -Comment[gl]=Transfire o menú do aplicativo ao escritorio +Comment[gl]=Transfire o menú de aplicación ao escritorio Comment[he]=מעביר את התפריטים של היישום אל שולחן העבודה Comment[hu]=Átviszi az alkalmazás menüjét az asztalra Comment[ia]=Il transfere menu de applicationes al scriptorio diff --git a/appmenu/menuimporter.cpp b/appmenu/menuimporter.cpp --- a/appmenu/menuimporter.cpp +++ b/appmenu/menuimporter.cpp @@ -69,7 +69,7 @@ KWindowInfo info(id, NET::WMWindowType, NET::WM2WindowClass); NET::WindowTypes mask = NET::AllTypesMask; - // Menu can try to register, right click in gimp for exemple + // Menu can try to register, right click in gimp for example if (info.windowType(mask) & (NET::Menu|NET::DropdownMenu|NET::PopupMenu)) { return; } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -1,6 +1,7 @@ add_definitions(-DTRANSLATION_DOMAIN=\"plasmashellprivateplugin\") install(DIRECTORY workspace/ DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/workspace/components) +add_subdirectory(containmentlayoutmanager) add_subdirectory(shellprivate) add_subdirectory(keyboardlayout) add_subdirectory(sessionsprivate) diff --git a/components/containmentlayoutmanager/CMakeLists.txt b/components/containmentlayoutmanager/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/CMakeLists.txt @@ -0,0 +1,26 @@ + + +set(containmentlayoutmanagerplugin_SRCS + containmentlayoutmanagerplugin.cpp + appletcontainer.cpp + configoverlay.cpp + appletslayout.cpp + abstractlayoutmanager.cpp + gridlayoutmanager.cpp + itemcontainer.cpp + resizehandle.cpp + ) + +add_library(containmentlayoutmanagerplugin ${containmentlayoutmanagerplugin_SRCS}) + +target_link_libraries(containmentlayoutmanagerplugin + PUBLIC + Qt5::Core + PRIVATE + Qt5::Qml Qt5::Quick + KF5::Plasma KF5::PlasmaQuick + ) + +install(TARGETS containmentlayoutmanagerplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/containmentlayoutmanager) + +install(DIRECTORY qml/ DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/containmentlayoutmanager) diff --git a/components/containmentlayoutmanager/abstractlayoutmanager.h b/components/containmentlayoutmanager/abstractlayoutmanager.h new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/abstractlayoutmanager.h @@ -0,0 +1,137 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#pragma once + +#include +#include "appletslayout.h" + +class ItemContainer; + +class AbstractLayoutManager : public QObject +{ + Q_OBJECT + +public: + AbstractLayoutManager(AppletsLayout *layout); + ~AbstractLayoutManager(); + + AppletsLayout *layout() const; + + void setCellSize(const QSizeF &size); + QSizeF cellSize() const; + + /** + * A size aligned to the gid that fully contains the given size + */ + QSizeF cellAlignedContainingSize(const QSizeF &size) const; + + /** + * Positions the item, does *not* assign the space as taken + */ + void positionItem(ItemContainer *item); + + /** + * Positions the item and assigns the space as taken by this item + */ + void positionItemAndAssign(ItemContainer *item); + + /** + * Set the space of item's rect as occupied by item. + * The operation may fail if some space of the item's geometry is already occupied. + * @returns true if the operation succeeded + */ + bool assignSpace(ItemContainer *item); + + /** + * If item is occupying space, set it as available + */ + void releaseSpace(ItemContainer *item); + +// VIRTUALS + virtual QString serializeLayout() const = 0; + + virtual void parseLayout(const QString &savedLayout) = 0; + + virtual void layoutGeometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + + /** + * true if the item is managed by the grid + */ + virtual bool itemIsManaged(ItemContainer *item) = 0; + + /** + * Forget about layout information and relayout all items based solely on their current geometry + */ + virtual void resetLayout() = 0; + + /** + * Forget about layout information and relayout all items based on their stored geometry first, and if that fails from their current geometry + */ + virtual void resetLayoutFromConfig() = 0; + + /** + * Restores an item geometry from the serialized config + * parseLayout needs to be called before this + * @returns true if the item was stored in the config + * and the restore has been performed. + * Otherwise, the item is not touched and returns false + */ + virtual bool restoreItem(ItemContainer *item) = 0; + + /** + * @returns true if the given rectangle is all free space + */ + virtual bool isRectAvailable(const QRectF &rect) = 0; + +Q_SIGNALS: + /** + * Emitted when the layout has been changed and now needs saving + */ + void layoutNeedsSaving(); + +protected: + /** + * Subclasses implement their assignSpace logic here + */ + virtual bool assignSpaceImpl(ItemContainer *item) = 0; + + /** + * Subclasses implement their releasespace logic here + */ + virtual void releaseSpaceImpl(ItemContainer *item) = 0; + + /** + * @returns a rectangle big at least as minimumSize, trying to be as near as possible to the current item's geometry, displaced in the direction we asked, forwards or backwards + * @param item the item container we want to place an item in + * @param minimumSize the minimum size we need to make sure is available + * @param direction the preferred item layout direction, can be Closest, LeftToRight, RightToLeft, TopToBottom, and BottomToTop + */ + virtual QRectF nextAvailableSpace(ItemContainer *item, const QSizeF &minimumSize, AppletsLayout::PreferredLayoutDirection direction) const = 0; + +private: + QRectF candidateGeometry(ItemContainer *item) const; + + AppletsLayout *m_layout; + + // size in pixels of a crid cell + QSizeF m_cellSize; +}; + diff --git a/components/containmentlayoutmanager/abstractlayoutmanager.cpp b/components/containmentlayoutmanager/abstractlayoutmanager.cpp new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/abstractlayoutmanager.cpp @@ -0,0 +1,143 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "abstractlayoutmanager.h" +#include "appletslayout.h" +#include "itemcontainer.h" + +#include + +AbstractLayoutManager::AbstractLayoutManager(AppletsLayout *layout) + : QObject(layout), + m_layout(layout) +{ +} + +AbstractLayoutManager::~AbstractLayoutManager() +{ +} + +AppletsLayout *AbstractLayoutManager::layout() const +{ + return m_layout; +} + +QSizeF AbstractLayoutManager::cellSize() const +{ + return m_cellSize; +} + +QSizeF AbstractLayoutManager::cellAlignedContainingSize(const QSizeF &size) const +{ + return QSizeF(m_cellSize.width() * ceil(size.width() / m_cellSize.width()), + m_cellSize.height() * ceil(size.height() / m_cellSize.height())); +} + +void AbstractLayoutManager::setCellSize(const QSizeF &size) +{ + m_cellSize = size; +} + +QRectF AbstractLayoutManager::candidateGeometry(ItemContainer *item) const +{ + const QRectF originalItemRect = QRectF(item->x(), item->y(), item->width(), item->height()); + + //TODO: a default minimum size + QSizeF minimumSize = QSize(m_layout->minimumItemWidth(), m_layout->minimumItemHeight()); + if (item->layoutAttached()) { + minimumSize = QSizeF(qMax(minimumSize.width(), item->layoutAttached()->property("minimumWidth").toReal()), + qMax(minimumSize.height(), item->layoutAttached()->property("minimumHeight").toReal())); + } + + const QRectF ltrRect = nextAvailableSpace(item, minimumSize, AppletsLayout::LeftToRight); + const QRectF rtlRect = nextAvailableSpace(item, minimumSize, AppletsLayout::RightToLeft); + const QRectF ttbRect = nextAvailableSpace(item, minimumSize, AppletsLayout::TopToBottom); + const QRectF bttRect = nextAvailableSpace(item, minimumSize, AppletsLayout::BottomToTop); + + // Take the closest rect, unless the item prefers a particular positioning strategy + QMap distances; + if (!ltrRect.isEmpty()) { + const int dist = item->preferredLayoutDirection() == AppletsLayout::LeftToRight ? 0 : QPointF(originalItemRect.center() - ltrRect.center()).manhattanLength(); + distances[dist] = ltrRect; + } + if (!rtlRect.isEmpty()) { + const int dist = item->preferredLayoutDirection() == AppletsLayout::RightToLeft ? 0 : QPointF(originalItemRect.center() - rtlRect.center()).manhattanLength(); + distances[dist] = rtlRect; + } + if (!ttbRect.isEmpty()) { + const int dist = item->preferredLayoutDirection() == AppletsLayout::TopToBottom ? 0 : QPointF(originalItemRect.center() - ttbRect.center()).manhattanLength(); + distances[dist] = ttbRect; + } + if (!bttRect.isEmpty()) { + const int dist = item->preferredLayoutDirection() == AppletsLayout::BottomToTop ? 0 : QPointF(originalItemRect.center() - bttRect.center()).manhattanLength(); + distances[dist] = bttRect; + } + + if (distances.isEmpty()) { + // Failure to layout, completely full + return originalItemRect; + } else { + return distances.first(); + } +} + +void AbstractLayoutManager::positionItem(ItemContainer *item) +{ + // Give it a sane size if uninitialized: this may change size hints + if (item->width() <= 0 || item->height() <= 0) { + item->setSize(QSizeF(qMax(m_layout->minimumItemWidth(), m_layout->defaultItemWidth()), + qMax(m_layout->minimumItemHeight(), m_layout->defaultItemHeight()))); + } + + QRectF candidate = candidateGeometry(item); + item->setPosition(candidate.topLeft()); + item->setSize(candidate.size()); +} + +void AbstractLayoutManager::positionItemAndAssign(ItemContainer *item) +{ + releaseSpace(item); + positionItem(item); + assignSpace(item); +} + +bool AbstractLayoutManager::assignSpace(ItemContainer *item) +{ + if (assignSpaceImpl(item)) { + emit layoutNeedsSaving(); + return true; + } else { + return false; + } +} + +void AbstractLayoutManager::releaseSpace(ItemContainer *item) +{ + releaseSpaceImpl(item); + emit layoutNeedsSaving(); +} + +void AbstractLayoutManager::layoutGeometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { + Q_UNUSED(newGeometry); + Q_UNUSED(oldGeometry); + // NOTE: Empty base implementation, don't put anything here +} + +#include "moc_abstractlayoutmanager.cpp" diff --git a/components/containmentlayoutmanager/appletcontainer.h b/components/containmentlayoutmanager/appletcontainer.h new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/appletcontainer.h @@ -0,0 +1,74 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#pragma once + +#include "itemcontainer.h" + +#include +#include + + +namespace PlasmaQuick { + class AppletQuickItem; +} + +class AppletContainer: public ItemContainer +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(PlasmaQuick::AppletQuickItem *applet READ applet NOTIFY appletChanged) + + Q_PROPERTY(QQmlComponent *busyIndicatorComponent READ busyIndicatorComponent WRITE setBusyIndicatorComponent NOTIFY busyIndicatorComponentChanged) + + Q_PROPERTY(QQmlComponent *configurationRequiredComponent READ configurationRequiredComponent WRITE setConfigurationRequiredComponent NOTIFY configurationRequiredComponentChanged) + +public: + AppletContainer(QQuickItem *parent = nullptr); + ~AppletContainer(); + + PlasmaQuick::AppletQuickItem *applet(); + + QQmlComponent *busyIndicatorComponent() const; + void setBusyIndicatorComponent(QQmlComponent *comp); + + QQmlComponent *configurationRequiredComponent() const; + void setConfigurationRequiredComponent(QQmlComponent *comp); + +protected: + void componentComplete() override; + +Q_SIGNALS: + void appletChanged(); + void busyIndicatorComponentChanged(); + void configurationRequiredComponentChanged(); + +private: + void connectBusyIndicator(); + void connectConfigurationRequired(); + + QPointer m_appletItem; + QPointer m_busyIndicatorComponent; + QQuickItem *m_busyIndicatorItem = nullptr; + QPointer m_configurationRequiredComponent; + QQuickItem *m_configurationRequiredItem = nullptr; +}; + diff --git a/components/containmentlayoutmanager/appletcontainer.cpp b/components/containmentlayoutmanager/appletcontainer.cpp new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/appletcontainer.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "appletcontainer.h" + +#include +#include + +#include +#include + +AppletContainer::AppletContainer(QQuickItem *parent) + : ItemContainer(parent) +{ + connect(this, &AppletContainer::contentItemChanged, this, [this]() { + if (m_appletItem) { + disconnect(m_appletItem->applet(), &Plasma::Applet::busyChanged, this, nullptr); + } + m_appletItem = qobject_cast(contentItem()); + + connectBusyIndicator(); + connectConfigurationRequired(); + + emit appletChanged(); + }); +} + +AppletContainer::~AppletContainer() +{ +} + +void AppletContainer::componentComplete() +{ + connectBusyIndicator(); + connectConfigurationRequired(); + ItemContainer::componentComplete(); +} + +PlasmaQuick::AppletQuickItem *AppletContainer::applet() +{ + return m_appletItem; +} + +QQmlComponent *AppletContainer::busyIndicatorComponent() const +{ + return m_busyIndicatorComponent; +} + +void AppletContainer::setBusyIndicatorComponent(QQmlComponent *component) +{ + if (m_busyIndicatorComponent == component) { + return; + } + + m_busyIndicatorComponent = component; + + if (m_busyIndicatorItem) { + m_busyIndicatorItem->deleteLater(); + m_busyIndicatorItem = nullptr; + } + + emit busyIndicatorComponentChanged(); +} + +void AppletContainer::connectBusyIndicator() +{ + if (m_appletItem && !m_busyIndicatorItem) { + Q_ASSERT(m_appletItem->applet()); + connect(m_appletItem->applet(), &Plasma::Applet::busyChanged, this, [this] () { + if (!m_busyIndicatorComponent || !m_appletItem->applet()->isBusy() || m_busyIndicatorItem) { + return; + } + + QQmlContext *context = QQmlEngine::contextForObject(this); + Q_ASSERT(context); + QObject *instance = m_busyIndicatorComponent->beginCreate(context); + m_busyIndicatorItem = qobject_cast(instance); + + if (!m_busyIndicatorItem) { + qWarning() << "Error: busyIndicatorComponent not of Item type"; + if (instance) { + instance->deleteLater(); + } + return; + } + + m_busyIndicatorItem->setParentItem(this); + m_busyIndicatorItem->setZ(999); + m_busyIndicatorComponent->completeCreate(); + }); + } +} + +QQmlComponent *AppletContainer::configurationRequiredComponent() const +{ + return m_configurationRequiredComponent; +} + +void AppletContainer::setConfigurationRequiredComponent(QQmlComponent *component) +{ + if (m_configurationRequiredComponent == component) { + return; + } + + m_configurationRequiredComponent = component; + + if (m_configurationRequiredItem) { + m_configurationRequiredItem->deleteLater(); + m_configurationRequiredItem = nullptr; + } + + emit configurationRequiredComponentChanged(); +} + +void AppletContainer::connectConfigurationRequired() +{ + if (m_appletItem && !m_configurationRequiredItem) { + Q_ASSERT(m_appletItem->applet()); + + auto syncConfigRequired = [this] () { + if (!m_configurationRequiredComponent || !m_appletItem->applet()->configurationRequired() || m_configurationRequiredItem) { + return; + } + + QQmlContext *context = QQmlEngine::contextForObject(this); + Q_ASSERT(context); + QObject *instance = m_configurationRequiredComponent->beginCreate(context); + m_configurationRequiredItem = qobject_cast(instance); + + if (!m_configurationRequiredItem) { + qWarning() << "Error: configurationRequiredComponent not of Item type"; + if (instance) { + instance->deleteLater(); + } + return; + } + + m_configurationRequiredItem->setParentItem(this); + m_configurationRequiredItem->setZ(998); + m_configurationRequiredComponent->completeCreate(); + }; + + connect(m_appletItem->applet(), &Plasma::Applet::configurationRequiredChanged, this, syncConfigRequired); + + if (m_appletItem->applet()->configurationRequired()) { + syncConfigRequired(); + } + } +} + +#include "moc_appletcontainer.cpp" diff --git a/components/containmentlayoutmanager/appletslayout.h b/components/containmentlayoutmanager/appletslayout.h new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/appletslayout.h @@ -0,0 +1,226 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#pragma once + +#include +#include +#include +#include + +class QTimer; + +namespace Plasma { + class Containment; +} + +namespace PlasmaQuick { + class AppletQuickItem; +} + +class AbstractLayoutManager; +class AppletContainer; +class ItemContainer; + +class AppletsLayout: public QQuickItem +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(QString configKey READ configKey WRITE setConfigKey NOTIFY configKeyChanged) + + Q_PROPERTY(PlasmaQuick::AppletQuickItem *containment READ containment WRITE setContainment NOTIFY containmentChanged) + + Q_PROPERTY(QJSValue acceptsAppletCallback READ acceptsAppletCallback WRITE setAcceptsAppletCallback NOTIFY acceptsAppletCallbackChanged) + + Q_PROPERTY(qreal minimumItemWidth READ minimumItemWidth WRITE setMinimumItemWidth NOTIFY minimumItemWidthChanged) + + Q_PROPERTY(qreal minimumItemHeight READ minimumItemHeight WRITE setMinimumItemHeight NOTIFY minimumItemHeightChanged) + + Q_PROPERTY(qreal defaultItemWidth READ defaultItemWidth WRITE setDefaultItemWidth NOTIFY defaultItemWidthChanged) + + Q_PROPERTY(qreal defaultItemHeight READ defaultItemHeight WRITE setDefaultItemHeight NOTIFY defaultItemHeightChanged) + + Q_PROPERTY(qreal cellWidth READ cellWidth WRITE setCellWidth NOTIFY cellWidthChanged) + + Q_PROPERTY(qreal cellHeight READ cellHeight WRITE setCellHeight NOTIFY cellHeightChanged) + + Q_PROPERTY(QQmlComponent *appletContainerComponent READ appletContainerComponent WRITE setAppletContainerComponent NOTIFY appletContainerComponentChanged) + + Q_PROPERTY(ItemContainer *placeHolder READ placeHolder WRITE setPlaceHolder NOTIFY placeHolderChanged); + + /** + * if the applets layout contains some kind of main MouseArea, + * MouseEventListener or Flickable, we want to filter its events to make the + * long mouse press work + */ + Q_PROPERTY(QQuickItem *eventManagerToFilter READ eventManagerToFilter WRITE setEventManagerToFilter NOTIFY eventManagerToFilterChanged); + + Q_PROPERTY(AppletsLayout::EditModeCondition editModeCondition READ editModeCondition WRITE setEditModeCondition NOTIFY editModeConditionChanged) + Q_PROPERTY(bool editMode READ editMode WRITE setEditMode NOTIFY editModeChanged) + +public: + enum PreferredLayoutDirection { + Closest = 0, + LeftToRight, + RightToLeft, + TopToBottom, + BottomToTop + }; + Q_ENUM(PreferredLayoutDirection) + + enum EditModeCondition { + Locked = 0, + Manual, + AfterPressAndHold, + }; + Q_ENUM(EditModeCondition) + + AppletsLayout(QQuickItem *parent = nullptr); + ~AppletsLayout(); + + // QML setters and getters + QString configKey() const; + void setConfigKey(const QString &key); + + PlasmaQuick::AppletQuickItem *containment() const; + void setContainment(PlasmaQuick::AppletQuickItem *containment); + + QJSValue acceptsAppletCallback() const; + void setAcceptsAppletCallback(const QJSValue& callback); + + qreal minimumItemWidth() const; + void setMinimumItemWidth(qreal width); + + qreal minimumItemHeight() const; + void setMinimumItemHeight(qreal height); + + qreal defaultItemWidth() const; + void setDefaultItemWidth(qreal width); + + qreal defaultItemHeight() const; + void setDefaultItemHeight(qreal height); + + qreal cellWidth() const; + void setCellWidth(qreal width); + + qreal cellHeight() const; + void setCellHeight(qreal height); + + QQmlComponent *appletContainerComponent() const; + void setAppletContainerComponent(QQmlComponent *component); + + ItemContainer *placeHolder() const; + void setPlaceHolder(ItemContainer *placeHolder); + + QQuickItem *eventManagerToFilter() const; + void setEventManagerToFilter(QQuickItem *item); + + EditModeCondition editModeCondition() const; + void setEditModeCondition(EditModeCondition condition); + + bool editMode() const; + void setEditMode(bool edit); + + Q_INVOKABLE void save(); + Q_INVOKABLE void showPlaceHolderAt(const QRectF &geom); + Q_INVOKABLE void showPlaceHolderForItem(ItemContainer *item); + Q_INVOKABLE void hidePlaceHolder(); + + Q_INVOKABLE bool isRectAvailable(qreal x, qreal y, qreal width, qreal height); + Q_INVOKABLE bool itemIsManaged(ItemContainer *item); + Q_INVOKABLE void positionItem(ItemContainer *item); + Q_INVOKABLE void restoreItem(ItemContainer *item); + Q_INVOKABLE void releaseSpace(ItemContainer *item); + +Q_SIGNALS: + /** + * An applet has been refused by the layout: acceptsAppletCallback + * returned false and will need to be managed in a different way + */ + void appletRefused(QObject *applet, int x, int y); + + void configKeyChanged(); + void containmentChanged(); + void minimumItemWidthChanged(); + void minimumItemHeightChanged(); + void defaultItemWidthChanged(); + void defaultItemHeightChanged(); + void cellWidthChanged(); + void cellHeightChanged(); + void acceptsAppletCallbackChanged(); + void appletContainerComponentChanged(); + void placeHolderChanged(); + void eventManagerToFilterChanged(); + void editModeConditionChanged(); + void editModeChanged(); + +protected: + bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; + void updatePolish() override; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + //void classBegin() override; + void componentComplete() override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseUngrabEvent() override; + +private Q_SLOTS: + void appletAdded(QObject *applet, int x, int y); + void appletRemoved(QObject *applet); + +private: + AppletContainer *createContainerForApplet(PlasmaQuick::AppletQuickItem *appletItem); + + + QString m_configKey; + QTimer *m_saveLayoutTimer; + QTimer *m_configKeyChangeTimer; + + PlasmaQuick::AppletQuickItem *m_containmentItem = nullptr; + Plasma::Containment *m_containment = nullptr; + QQmlComponent *m_appletContainerComponent = nullptr; + + AbstractLayoutManager *m_layoutManager = nullptr; + + QPointer m_placeHolder; + QPointer m_eventManagerToFilter; + + QTimer *m_pressAndHoldTimer; + QTimer *m_sizeSyncTimer; + + QJSValue m_acceptsAppletCallback; + + AppletsLayout::EditModeCondition m_editModeCondition = AppletsLayout::Manual; + + QHash m_containerForApplet; + + QSizeF m_minimumItemSize; + QSizeF m_defaultItemSize; + QSizeF m_savedSize; + QRectF m_geometryBeforeResolutionChange; + + QPointF m_mouseDownPosition = QPoint(-1, -1); + bool m_mouseDownWasEditMode = false; + bool m_editMode = false; +}; + diff --git a/components/containmentlayoutmanager/appletslayout.cpp b/components/containmentlayoutmanager/appletslayout.cpp new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/appletslayout.cpp @@ -0,0 +1,742 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "appletslayout.h" +#include "appletcontainer.h" +#include "gridlayoutmanager.h" + +#include +#include +#include +#include + +// Plasma +#include +#include +#include + +AppletsLayout::AppletsLayout(QQuickItem *parent) + : QQuickItem(parent) +{ + m_layoutManager = new GridLayoutManager(this); + + setFlags(QQuickItem::ItemIsFocusScope); + setAcceptedMouseButtons(Qt::LeftButton); + + m_saveLayoutTimer = new QTimer(this); + m_saveLayoutTimer->setSingleShot(true); + m_saveLayoutTimer->setInterval(100); + connect(m_layoutManager, &AbstractLayoutManager::layoutNeedsSaving, m_saveLayoutTimer, QOverload<>::of(&QTimer::start)); + connect(m_saveLayoutTimer, &QTimer::timeout, this, [this] () { + if (!m_configKey.isEmpty() && m_containment && m_containment->corona()->isStartupCompleted()) { + const QString serializedConfig = m_layoutManager->serializeLayout(); + m_containment->config().writeEntry(m_configKey, serializedConfig); + //FIXME: something more efficient + m_layoutManager->parseLayout(serializedConfig); + m_savedSize = size(); + m_containment->corona()->requireConfigSync(); + } + }); + + m_configKeyChangeTimer = new QTimer(this); + m_configKeyChangeTimer->setSingleShot(true); + m_configKeyChangeTimer->setInterval(100); + connect(m_configKeyChangeTimer, &QTimer::timeout, this, [this] () { + if (!m_configKey.isEmpty() && m_containment) { + m_layoutManager->parseLayout(m_containment->config().readEntry(m_configKey, "")); + + if (width() > 0 && height() > 0) { + m_layoutManager->resetLayoutFromConfig(); + m_savedSize = size(); + } + } + }); + m_pressAndHoldTimer = new QTimer(this); + m_pressAndHoldTimer->setSingleShot(true); + connect(m_pressAndHoldTimer, &QTimer::timeout, this, [this]() { + setEditMode(true); + }); + + m_sizeSyncTimer = new QTimer(this); + m_sizeSyncTimer->setSingleShot(true); + m_sizeSyncTimer->setInterval(150); + connect(m_sizeSyncTimer, &QTimer::timeout, this, [this]() { + const QRect newGeom(x(), y(), width(), height()); + // The size has been restored from the last one it has been saved: restore that exact same layout + if (newGeom.size() == m_savedSize) { + m_layoutManager->resetLayoutFromConfig(); + + // If the resize is consequence of a screen resolution change, queue a relayout maintaining the distance between screen edges + } else if (!m_geometryBeforeResolutionChange.isEmpty()) { + m_layoutManager->layoutGeometryChanged(newGeom, m_geometryBeforeResolutionChange); + m_geometryBeforeResolutionChange = QRectF(); + + // Heuristically relayout items only when the plasma startup is fully completed + } else { + polish(); + } + }); +} + +AppletsLayout::~AppletsLayout() +{ +} + +PlasmaQuick::AppletQuickItem *AppletsLayout::containment() const +{ + return m_containmentItem; +} + +void AppletsLayout::setContainment(PlasmaQuick::AppletQuickItem *containmentItem) +{ + // Forbid changing containmentItem at runtime + if (m_containmentItem + || containmentItem == m_containmentItem + || !containmentItem->applet() + || !containmentItem->applet()->isContainment()) { + qWarning() << "Error: cannot change the containment to AppletsLayout"; + return; + } + + // Can't assign containments that aren't parents + QQuickItem *candidate = parentItem(); + while (candidate) { + if (candidate == m_containmentItem) { + break; + } + candidate = candidate->parentItem(); + } + if (candidate != m_containmentItem) { + return; + } + + m_containmentItem = containmentItem; + m_containment = static_cast(m_containmentItem->applet()); + + connect(m_containmentItem, SIGNAL(appletAdded(QObject *, int, int)), + this, SLOT(appletAdded(QObject *, int, int))); + + connect(m_containmentItem, SIGNAL(appletRemoved(QObject *)), + this, SLOT(appletRemoved(QObject *))); + + emit containmentChanged(); +} + +QString AppletsLayout::configKey() const +{ + return m_configKey; +} + +void AppletsLayout::setConfigKey(const QString &key) +{ + if (m_configKey == key) { + return; + } + + m_configKey = key; + + // Reloading everything from the new config is expansive, event compress it + m_configKeyChangeTimer->start(); + + emit configKeyChanged(); +} + +QJSValue AppletsLayout::acceptsAppletCallback() const +{ + return m_acceptsAppletCallback; +} + +qreal AppletsLayout::minimumItemWidth() const +{ + return m_minimumItemSize.width(); +} + +void AppletsLayout::setMinimumItemWidth(qreal width) +{ + if (qFuzzyCompare(width, m_minimumItemSize.width())) { + return; + } + + m_minimumItemSize.setWidth(width); + + emit minimumItemWidthChanged(); +} + +qreal AppletsLayout::minimumItemHeight() const +{ + return m_minimumItemSize.height(); +} + +void AppletsLayout::setMinimumItemHeight(qreal height) +{ + if (qFuzzyCompare(height, m_minimumItemSize.height())) { + return; + } + + m_minimumItemSize.setHeight(height); + + emit minimumItemHeightChanged(); +} + +qreal AppletsLayout::defaultItemWidth() const +{ + return m_defaultItemSize.width(); +} + +void AppletsLayout::setDefaultItemWidth(qreal width) +{ + if (qFuzzyCompare(width, m_defaultItemSize.width())) { + return; + } + + m_defaultItemSize.setWidth(width); + + emit defaultItemWidthChanged(); +} + +qreal AppletsLayout::defaultItemHeight() const +{ + return m_defaultItemSize.height(); +} + +void AppletsLayout::setDefaultItemHeight(qreal height) +{ + if (qFuzzyCompare(height, m_defaultItemSize.height())) { + return; + } + + m_defaultItemSize.setHeight(height); + + emit defaultItemHeightChanged(); +} + +qreal AppletsLayout::cellWidth() const +{ + return m_layoutManager->cellSize().width(); +} + +void AppletsLayout::setCellWidth(qreal width) +{ + if (qFuzzyCompare(width, m_layoutManager->cellSize().width())) { + return; + } + + m_layoutManager->setCellSize(QSizeF(width, m_layoutManager->cellSize().height())); + + emit cellWidthChanged(); +} + +qreal AppletsLayout::cellHeight() const +{ + return m_layoutManager->cellSize().height(); +} + +void AppletsLayout::setCellHeight(qreal height) +{ + if (qFuzzyCompare(height, m_layoutManager->cellSize().height())) { + return; + } + + m_layoutManager->setCellSize(QSizeF(m_layoutManager->cellSize().width(), height)); + + emit cellHeightChanged(); +} + +void AppletsLayout::setAcceptsAppletCallback(const QJSValue& callback) +{ + if (m_acceptsAppletCallback.strictlyEquals(callback)) { + return; + } + + if (!callback.isNull() && !callback.isCallable()) { + return; + } + + m_acceptsAppletCallback = callback; + + Q_EMIT acceptsAppletCallbackChanged(); +} + +QQmlComponent *AppletsLayout::appletContainerComponent() const +{ + return m_appletContainerComponent; +} + +void AppletsLayout::setAppletContainerComponent(QQmlComponent *component) +{ + if (m_appletContainerComponent == component) { + return; + } + + m_appletContainerComponent = component; + + emit appletContainerComponentChanged(); +} + +AppletsLayout::EditModeCondition AppletsLayout::editModeCondition() const +{ + return m_editModeCondition; +} + +void AppletsLayout::setEditModeCondition(AppletsLayout::EditModeCondition condition) +{ + if (m_editModeCondition == condition) { + return; + } + + if (m_editModeCondition == Locked) { + setEditMode(false); + } + + m_editModeCondition = condition; + + emit editModeConditionChanged(); +} + +bool AppletsLayout::editMode() const +{ + return m_editMode; +} + +void AppletsLayout::setEditMode(bool editMode) +{ + if (m_editMode == editMode) { + return; + } + + m_editMode = editMode; + + emit editModeChanged(); +} + +ItemContainer *AppletsLayout::placeHolder() const +{ + return m_placeHolder; +} + +void AppletsLayout::setPlaceHolder(ItemContainer *placeHolder) +{ + if (m_placeHolder == placeHolder) { + return; + } + + m_placeHolder = placeHolder; + m_placeHolder->setParentItem(this); + m_placeHolder->setZ(9999); + m_placeHolder->setOpacity(false); + + emit placeHolderChanged(); +} + +QQuickItem *AppletsLayout::eventManagerToFilter() const +{ + return m_eventManagerToFilter; +} + +void AppletsLayout::setEventManagerToFilter(QQuickItem *item) +{ + if (m_eventManagerToFilter == item) { + return; + } + + m_eventManagerToFilter = item; + setFiltersChildMouseEvents(m_eventManagerToFilter); + emit eventManagerToFilterChanged(); +} + + +void AppletsLayout::save() +{ + m_saveLayoutTimer->start(); +} + +void AppletsLayout::showPlaceHolderAt(const QRectF &geom) +{ + if (!m_placeHolder) { + return; + } + + m_placeHolder->setPosition(geom.topLeft()); + m_placeHolder->setSize(geom.size()); + + m_layoutManager->positionItem(m_placeHolder); + + m_placeHolder->setProperty("opacity", 1); +} + +void AppletsLayout::showPlaceHolderForItem(ItemContainer *item) +{ + if (!m_placeHolder) { + return; + } + + m_placeHolder->setPreferredLayoutDirection(item->preferredLayoutDirection()); + m_placeHolder->setPosition(item->position()); + m_placeHolder->setSize(item->size()); + + m_layoutManager->positionItem(m_placeHolder); + + m_placeHolder->setProperty("opacity", 1); +} + +void AppletsLayout::hidePlaceHolder() +{ + if (!m_placeHolder) { + return; + } + + m_placeHolder->setProperty("opacity", 0); +} + +bool AppletsLayout::isRectAvailable(qreal x, qreal y, qreal width, qreal height) +{ + return m_layoutManager->isRectAvailable(QRectF(x, y, width, height)); +} + +bool AppletsLayout::itemIsManaged(ItemContainer *item) +{ + if (!item) { + return false; + } + + return m_layoutManager->itemIsManaged(item); +} + +void AppletsLayout::positionItem(ItemContainer *item) +{ + if (!item) { + return; + } + + item->setParent(this); + m_layoutManager->positionItemAndAssign(item); +} + +void AppletsLayout::restoreItem(ItemContainer *item) +{ + m_layoutManager->restoreItem(item); +} + +void AppletsLayout::releaseSpace(ItemContainer *item) +{ + if (!item) { + return; + } + + m_layoutManager->releaseSpace(item); +} + +void AppletsLayout::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + // Ignore completely moves without resize + if (newGeometry.size() == oldGeometry.size()) { + QQuickItem::geometryChanged(newGeometry, oldGeometry); + return; + } + + // Don't care for anything happening before startup completion + if (!m_containment || !m_containment->corona() || !m_containment->corona()->isStartupCompleted()) { + QQuickItem::geometryChanged(newGeometry, oldGeometry); + return; + } + + // Only do a layouting procedure if we received a valid size + if (!newGeometry.isEmpty()) { + m_sizeSyncTimer->start(); + } + + QQuickItem::geometryChanged(newGeometry, oldGeometry); +} + +void AppletsLayout::updatePolish() +{ + m_layoutManager->resetLayout(); + m_savedSize = size(); +} + +void AppletsLayout::componentComplete() +{ + if (!m_containment || !m_containmentItem) { + QQuickItem::componentComplete(); + return; + } + + if (!m_configKey.isEmpty()) { + m_layoutManager->parseLayout(m_containment->config().readEntry(m_configKey, "")); + } + + QList appletObjects = m_containmentItem->property("applets").value >(); + + for (auto *obj : appletObjects) { + PlasmaQuick::AppletQuickItem *appletItem = qobject_cast(obj); + + if (!obj) { + continue; + } + + AppletContainer *container = createContainerForApplet(appletItem); + if (width() > 0 && height() > 0) { + m_layoutManager->positionItemAndAssign(container); + } + } + + //layout all extra non applet items + if (width() > 0 && height() > 0) { + for (auto *child : childItems()) { + ItemContainer *item = qobject_cast(child); + if (item && item != m_placeHolder && !m_layoutManager->itemIsManaged(item)) { + m_layoutManager->positionItemAndAssign(item); + } + } + } + + if (m_containment && m_containment->corona()) { + connect(m_containment->corona(), &Plasma::Corona::startupCompleted, this, [](){ + // m_savedSize = size(); + }); + // When the screen geometry changes, we need to know the geometry just before it did, so we can apply out heuristic of keeping the distance with borders constant + connect(m_containment->corona(), &Plasma::Corona::screenGeometryChanged, this, [this](int id){ + if (m_containment->screen() == id) { + m_geometryBeforeResolutionChange = QRectF(x(), y(), width(), height()); + } + }); + } + QQuickItem::componentComplete(); +} + + +bool AppletsLayout::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + if (item != m_eventManagerToFilter) { + return QQuickItem::childMouseEventFilter(item, event); + } + + switch (event->type()) { + case QEvent::MouseButtonPress: { + QMouseEvent *me = static_cast(event); + if (me->buttons() & Qt::LeftButton) { + mousePressEvent(me); + } + break; + } + case QEvent::MouseMove: { + QMouseEvent *me = static_cast(event); + mouseMoveEvent(me); + break; + } + case QEvent::MouseButtonRelease: { + QMouseEvent *me = static_cast(event); + mouseReleaseEvent(me); + break; + } + case QEvent::UngrabMouse: + mouseUngrabEvent(); + break; + default: + break; + } + + return QQuickItem::childMouseEventFilter(item, event); +} + +void AppletsLayout::mousePressEvent(QMouseEvent *event) +{ + forceActiveFocus(Qt::MouseFocusReason); + + if (!m_editMode && m_editModeCondition == AppletsLayout::Manual) { + return; + } + + if (!m_editMode && m_editModeCondition == AppletsLayout::AfterPressAndHold) { + m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval()); + } + + m_mouseDownWasEditMode = m_editMode; + m_mouseDownPosition = event->windowPos(); + + //event->setAccepted(false); +} + +void AppletsLayout::mouseMoveEvent(QMouseEvent *event) +{ + if (!m_editMode && m_editModeCondition == AppletsLayout::Manual) { + return; + } + + if (!m_editMode + && QPointF(event->windowPos() - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) { + m_pressAndHoldTimer->stop(); + } +} + +void AppletsLayout::mouseReleaseEvent(QMouseEvent *event) +{ + if (m_editMode + && m_mouseDownWasEditMode + // By only accepting synthetyzed events, this makes the + // close by tapping in any empty area only work with real + // touch events, as we want a different behavior between desktop + // and tablet mode + && (event->source() == Qt::MouseEventSynthesizedBySystem + || event->source() == Qt::MouseEventSynthesizedByQt) + && QPointF(event->windowPos() - m_mouseDownPosition).manhattanLength() < QGuiApplication::styleHints()->startDragDistance()) { + setEditMode(false); + } + + m_pressAndHoldTimer->stop(); + + if (!m_editMode) { + for (auto *child : childItems()) { + ItemContainer *item = qobject_cast(child); + if (item && item != m_placeHolder) { + item->setEditMode(false); + } + } + } +} + +void AppletsLayout::mouseUngrabEvent() +{ + m_pressAndHoldTimer->stop(); +} + +void AppletsLayout::appletAdded(QObject *applet, int x, int y) +{ + PlasmaQuick::AppletQuickItem *appletItem = qobject_cast(applet); + + //maybe even an assert? + if (!appletItem) { + return; + } + + if (m_acceptsAppletCallback.isCallable()) { + QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine(); + Q_ASSERT(engine); + QJSValueList args; + args << engine->newQObject(applet) << QJSValue(x) << QJSValue(y); + + if (!m_acceptsAppletCallback.call(args).toBool()) { + emit appletRefused(applet, x, y); + return; + } + } + + AppletContainer *container = createContainerForApplet(appletItem); + container->setPosition(QPointF(x, y)); + container->setVisible(true); + + m_layoutManager->positionItemAndAssign(container); +} + +void AppletsLayout::appletRemoved(QObject *applet) +{ + PlasmaQuick::AppletQuickItem *appletItem = qobject_cast(applet); + + //maybe even an assert? + if (!appletItem) { + return; + } + + AppletContainer *container = m_containerForApplet.value(appletItem); + if (!container) { + return; + } + + m_layoutManager->releaseSpace(container); + m_containerForApplet.remove(appletItem); + appletItem->setParentItem(this); + container->deleteLater(); +} + +AppletContainer *AppletsLayout::createContainerForApplet(PlasmaQuick::AppletQuickItem *appletItem) +{ + AppletContainer *container = m_containerForApplet.value(appletItem); + + if (container) { + return container; + } + + bool createdFromQml = true; + + if (m_appletContainerComponent) { + QQmlContext *context = QQmlEngine::contextForObject(this); + Q_ASSERT(context); + QObject *instance = m_appletContainerComponent->beginCreate(context); + container = qobject_cast(instance); + if (container) { + container->setParentItem(this); + } else { + qWarning() << "Error: provided component not an AppletContainer instance"; + if (instance) { + instance->deleteLater(); + } + createdFromQml = false; + } + } + + if (!container) { + container = new AppletContainer(this); + } + + container->setVisible(false); + + const QSizeF appletSize = appletItem->size(); + container->setContentItem(appletItem); + + m_containerForApplet[appletItem] = container; + container->setLayout(this); + container->setKey(QLatin1String("Applet-") + QString::number(appletItem->applet()->id())); + + const bool geometryWasSaved = m_layoutManager->restoreItem(container); + + if (!geometryWasSaved) { + container->setPosition(QPointF(appletItem->x() - container->leftPadding(), + appletItem->y() - container->topPadding())); + + if (!appletSize.isEmpty()) { + container->setSize(QSizeF(qMax(m_minimumItemSize.width(), appletSize.width() + container->leftPadding() + container->rightPadding()), + qMax(m_minimumItemSize.height(), appletSize.height() + container->topPadding() + container->bottomPadding()))); + } + } + + if (m_appletContainerComponent && createdFromQml) { + m_appletContainerComponent->completeCreate(); + } + + //NOTE: This has to be done here as we need the component completed to have all the bindings evaluated + if (!geometryWasSaved && appletSize.isEmpty()) { + if (container->initialSize().width() > m_minimumItemSize.width() && + container->initialSize().height() > m_minimumItemSize.height()) { + const QSizeF size = m_layoutManager->cellAlignedContainingSize( container->initialSize()); + container->setSize(size); + } else { + container->setSize(QSizeF(qMax(m_minimumItemSize.width(), m_defaultItemSize.width()), + qMax(m_minimumItemSize.height(), m_defaultItemSize.height()))); + } + } + + container->setVisible(true); + appletItem->setVisible(true); + + + return container; +} + +#include "moc_appletslayout.cpp" diff --git a/components/containmentlayoutmanager/configoverlay.h b/components/containmentlayoutmanager/configoverlay.h new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/configoverlay.h @@ -0,0 +1,82 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#pragma once + +#include +#include + +#include "itemcontainer.h" + +class ConfigOverlay: public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(bool open READ open WRITE setOpen NOTIFY openChanged) + Q_PROPERTY(ItemContainer *itemContainer READ itemContainer NOTIFY itemContainerChanged) + Q_PROPERTY(qreal leftAvailableSpace READ leftAvailableSpace NOTIFY leftAvailableSpaceChanged); + Q_PROPERTY(qreal topAvailableSpace READ topAvailableSpace NOTIFY topAvailableSpaceChanged); + Q_PROPERTY(qreal rightAvailableSpace READ rightAvailableSpace NOTIFY rightAvailableSpaceChanged); + Q_PROPERTY(qreal bottomAvailableSpace READ bottomAvailableSpace NOTIFY bottomAvailableSpaceChanged); + Q_PROPERTY(bool touchInteraction READ touchInteraction NOTIFY touchInteractionChanged) + +public: + ConfigOverlay(QQuickItem *parent = nullptr); + ~ConfigOverlay(); + + ItemContainer *itemContainer() const; + // NOTE: setter not accessible from QML by purpose + void setItemContainer(ItemContainer *container); + + bool open() const; + void setOpen(bool open); + + qreal leftAvailableSpace() {return m_leftAvailableSpace;} + qreal topAvailableSpace() {return m_topAvailableSpace;} + qreal rightAvailableSpace() {return m_rightAvailableSpace;} + qreal bottomAvailableSpace() {return m_bottomAvailableSpace;} + + bool touchInteraction() const; + // This only usable from C++ + void setTouchInteraction(bool touch); + +Q_SIGNALS: + void openChanged(); + void itemContainerChanged(); + void leftAvailableSpaceChanged(); + void topAvailableSpaceChanged(); + void rightAvailableSpaceChanged(); + void bottomAvailableSpaceChanged(); + void touchInteractionChanged(); + +private: + QPointer m_itemContainer; + qreal m_leftAvailableSpace = 0; + qreal m_topAvailableSpace = 0; + qreal m_rightAvailableSpace = 0; + qreal m_bottomAvailableSpace = 0; + + QTimer *m_hideTimer = nullptr; + + QList m_oldTouchPoints; + + bool m_open = false; + bool m_touchInteraction = false; +}; + diff --git a/components/containmentlayoutmanager/configoverlay.cpp b/components/containmentlayoutmanager/configoverlay.cpp new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/configoverlay.cpp @@ -0,0 +1,133 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "configoverlay.h" + +#include + +ConfigOverlay::ConfigOverlay(QQuickItem *parent) + : QQuickItem(parent) +{ + m_hideTimer = new QTimer(this); + m_hideTimer->setSingleShot(true); + m_hideTimer->setInterval(600); + connect(m_hideTimer, &QTimer::timeout, this, [this] () { + setVisible(false); + }); +} + +ConfigOverlay::~ConfigOverlay() +{ +} + +bool ConfigOverlay::open() const +{ + return m_open; +} + +void ConfigOverlay::setOpen(bool open) +{ + if (open == m_open) { + return; + } + + m_open = open; + + if (open) { + m_hideTimer->stop(); + setVisible(true); + } else { + m_hideTimer->start(); + } + + emit openChanged(); +} + +bool ConfigOverlay::touchInteraction() const +{ + return m_touchInteraction; +} +void ConfigOverlay::setTouchInteraction(bool touch) +{ + if (touch == m_touchInteraction) { + return; + } + + m_touchInteraction = touch; + emit touchInteractionChanged(); +} + +ItemContainer *ConfigOverlay::itemContainer() const +{ + return m_itemContainer; +} + +void ConfigOverlay::setItemContainer(ItemContainer *container) +{ + if (container == m_itemContainer) { + return; + } + + if (m_itemContainer) { + disconnect(m_itemContainer, nullptr, this, nullptr); + } + + m_itemContainer = container; + + if (!m_itemContainer || !m_itemContainer->layout()) { + return; + } + + m_leftAvailableSpace = qMax(0.0, m_itemContainer->x()); + m_rightAvailableSpace = qMax(0.0, m_itemContainer->layout()->width() - (m_itemContainer->x() + m_itemContainer->width())); + m_topAvailableSpace = qMax(0.0, m_itemContainer->y()); + m_bottomAvailableSpace = qMax(0.0, m_itemContainer->layout()->height() - (m_itemContainer->y() + m_itemContainer->height())); + emit leftAvailableSpaceChanged(); + emit rightAvailableSpaceChanged(); + emit topAvailableSpaceChanged(); + emit bottomAvailableSpaceChanged(); + + connect(m_itemContainer.data(), &ItemContainer::xChanged, this, [this] () { + m_leftAvailableSpace = qMax(0.0, m_itemContainer->x()); + m_rightAvailableSpace = qMax(0.0, m_itemContainer->layout()->width() - (m_itemContainer->x() + m_itemContainer->width())); + emit leftAvailableSpaceChanged(); + emit rightAvailableSpaceChanged(); + }); + + connect(m_itemContainer.data(), &ItemContainer::yChanged, this, [this] () { + m_topAvailableSpace = qMax(0.0, m_itemContainer->y()); + m_bottomAvailableSpace = qMax(0.0, m_itemContainer->layout()->height() - (m_itemContainer->y() + m_itemContainer->height())); + emit topAvailableSpaceChanged(); + emit bottomAvailableSpaceChanged(); + }); + + connect(m_itemContainer.data(), &ItemContainer::widthChanged, this, [this] () { + m_rightAvailableSpace = qMax(0.0, m_itemContainer->layout()->width() - (m_itemContainer->x() + m_itemContainer->width())); + emit rightAvailableSpaceChanged(); + }); + + connect(m_itemContainer.data(), &ItemContainer::heightChanged, this, [this] () { + m_bottomAvailableSpace = qMax(0.0, m_itemContainer->layout()->height() - (m_itemContainer->y() + m_itemContainer->height())); + emit bottomAvailableSpaceChanged(); + }); + emit itemContainerChanged(); +} + +#include "moc_configoverlay.cpp" diff --git a/components/containmentlayoutmanager/containmentlayoutmanagerplugin.h b/components/containmentlayoutmanager/containmentlayoutmanagerplugin.h new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/containmentlayoutmanagerplugin.h @@ -0,0 +1,35 @@ +/* + * Copyright 2019 by Marco Martin + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +#include +#include + +class ContainmentLayoutManagerPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override; +}; + diff --git a/components/containmentlayoutmanager/containmentlayoutmanagerplugin.cpp b/components/containmentlayoutmanager/containmentlayoutmanagerplugin.cpp new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/containmentlayoutmanagerplugin.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2019 by Marco Martin + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "containmentlayoutmanagerplugin.h" + +#include +#include +#include + +#include "appletslayout.h" +#include "appletcontainer.h" +#include "configoverlay.h" +#include "itemcontainer.h" +#include "resizehandle.h" + +void ContainmentLayoutManagerPlugin::registerTypes(const char *uri) +{ + Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.containmentlayoutmanager")); + + qmlRegisterType(uri, 1, 0, "AppletsLayout"); + qmlRegisterType(uri, 1, 0, "AppletContainer"); + qmlRegisterType(uri, 1, 0, "ConfigOverlay"); + qmlRegisterType(uri, 1, 0, "ItemContainer"); + qmlRegisterType(uri, 1, 0, "ResizeHandle"); + + // qmlProtectModule(uri, 1); +} + +#include "moc_containmentlayoutmanagerplugin.cpp" + diff --git a/components/containmentlayoutmanager/gridlayoutmanager.h b/components/containmentlayoutmanager/gridlayoutmanager.h new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/gridlayoutmanager.h @@ -0,0 +1,114 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#pragma once + +#include "abstractlayoutmanager.h" +#include "appletcontainer.h" + +class AppletsLayout; +class ItemContainer; + +struct Geom { + int x; + int y; + int width; + int height; + int rotation; +}; + +class GridLayoutManager : public AbstractLayoutManager +{ + Q_OBJECT + +public: + GridLayoutManager(AppletsLayout *layout); + ~GridLayoutManager(); + + void layoutGeometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + QString serializeLayout() const override; + void parseLayout(const QString &savedLayout) override; + + bool itemIsManaged(ItemContainer *item) override; + + void resetLayout() override; + void resetLayoutFromConfig() override; + + bool restoreItem(ItemContainer *item) override; + + bool isRectAvailable(const QRectF &rect) override; + + + +protected: + // The rectangle as near as possible to the current item geometry which can fit it + QRectF nextAvailableSpace(ItemContainer *item, const QSizeF &minimumSize, AppletsLayout::PreferredLayoutDirection direction) const override; + + bool assignSpaceImpl(ItemContainer *item) override; + void releaseSpaceImpl(ItemContainer *item) override; + +private: + // Total cell rows + inline int rows() const; + + // Total cell columns + inline int columns() const; + + // Converts the item pixel-based geometry to a cellsize-based geometry + inline QRect cellBasedGeometry(const QRectF &geom) const; + + // Converts the item pixel-based geometry to a cellsize-based geometry + // This is the bounding geometry, usually larger than cellBasedGeometry + inline QRect cellBasedBoundingGeometry(const QRectF &geom) const; + + // true if the cell is out of the bounds of the containment + inline bool isOutOfBounds(const QPair &cell) const; + + // True if the space for the given cell is available + inline bool isCellAvailable(const QPair &cell) const; + + // Returns the qrect geometry for an item + inline QRectF itemGeometry(QQuickItem *item) const; + + // The next cell given the direction + QPair nextCell(const QPair &cell, AppletsLayout::PreferredLayoutDirection direction) const; + + // The next cell that is available given the direction + QPair nextAvailableCell(const QPair &cell, AppletsLayout::PreferredLayoutDirection direction) const; + + // The next cell that is has something in it given the direction + QPair nextTakenCell(const QPair &cell, AppletsLayout::PreferredLayoutDirection direction) const; + + // How many cells are available in the row starting from the given cell and direction + int freeSpaceInDirection(const QPair &cell, AppletsLayout::PreferredLayoutDirection direction) const; + + /** + * This reacts to changes in size hints by an item + */ + void adjustToItemSizeHints(ItemContainer *item); + + // What is the item that occupies the point. The point is expressed in cells rather than pixels. a qpair rather a QPointF as QHash doesn't support identification by QPointF + QHash , ItemContainer *> m_grid; + QHash > > m_pointsForItem; + + QHash m_parsedConfig; +}; + diff --git a/components/containmentlayoutmanager/gridlayoutmanager.cpp b/components/containmentlayoutmanager/gridlayoutmanager.cpp new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/gridlayoutmanager.cpp @@ -0,0 +1,596 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gridlayoutmanager.h" +#include "appletslayout.h" +#include + +GridLayoutManager::GridLayoutManager(AppletsLayout *layout) + : AbstractLayoutManager(layout) +{ +} + +GridLayoutManager::~GridLayoutManager() +{ +} + +QString GridLayoutManager::serializeLayout() const +{ + QString result; + + for (auto *item : layout()->childItems()) { + ItemContainer *itemCont = qobject_cast(item); + if (itemCont && itemCont != layout()->placeHolder()) { + result += itemCont->key() + QLatin1Char(':') + + QString::number(itemCont->x()) + QLatin1Char(',') + + QString::number(itemCont->y()) + QLatin1Char(',') + + QString::number(itemCont->width()) + QLatin1Char(',') + + QString::number(itemCont->height()) + QLatin1Char(',') + + QString::number(itemCont->rotation()) + QLatin1Char(';'); + } + } + + return result; +} + +void GridLayoutManager::parseLayout(const QString &savedLayout) +{ + m_parsedConfig.clear(); + QStringList itemsConfigs = savedLayout.split(QLatin1Char(';')); + + for (const auto &itemString : itemsConfigs) { + QStringList itemConfig = itemString.split(QLatin1Char(':')); + if (itemConfig.count() != 2) { + continue; + } + + QString id = itemConfig[0]; + QStringList itemGeom = itemConfig[1].split(QLatin1Char(',')); + if (itemGeom.count() != 5) { + continue; + } + + m_parsedConfig[id] = {itemGeom[0].toInt(), itemGeom[1].toInt(), itemGeom[2].toInt(), itemGeom[3].toInt(), itemGeom[4].toInt()}; + } +} + +bool GridLayoutManager::itemIsManaged(ItemContainer *item) +{ + return m_pointsForItem.contains(item); +} + +inline void maintainItemEdgeAlignment(ItemContainer *item, const QRectF &newRect, const QRectF &oldRect) +{ + const qreal leftDist = item->x() - oldRect.x(); + const qreal hCenterDist = item->x() + item->width()/2 - oldRect.center().x(); + const qreal rightDist = oldRect.right() - item->x() - item->width(); + + qreal hMin = qMin(qMin(qAbs(leftDist), qAbs(hCenterDist)), qAbs(rightDist)); + if (qFuzzyCompare(hMin, qAbs(leftDist))) { + // Right alignment, do nothing + } else if (qFuzzyCompare(hMin, qAbs(hCenterDist))) { + item->setX(newRect.center().x() - item->width()/2 + hCenterDist); + } else if (qFuzzyCompare(hMin, qAbs(rightDist))) { + item->setX(newRect.right() - item->width() - rightDist ); + } + + const qreal topDist = item->y() - oldRect.y(); + const qreal vCenterDist = item->y() + item->height()/2 - oldRect.center().y(); + const qreal bottomDist = oldRect.bottom() - item->y() - item->height(); + + qreal vMin = qMin(qMin(qAbs(topDist), qAbs(vCenterDist)), qAbs(bottomDist)); + + if (qFuzzyCompare(vMin, qAbs(topDist))) { + // Top alignment, do nothing + } else if (qFuzzyCompare(vMin, qAbs(vCenterDist))) { + item->setY(newRect.center().y() - item->height()/2 + vCenterDist); + } else if (qFuzzyCompare(vMin, qAbs(bottomDist))) { + item->setY(newRect.bottom() - item->height() - bottomDist ); + } +} + +void GridLayoutManager::layoutGeometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + m_grid.clear(); + m_pointsForItem.clear(); + for (auto *item : layout()->childItems()) { + // Stash the old config + //m_parsedConfig[item->key()] = {item->x(), item->y(), item->width(), item->height(), item->rotation()}; + // Move the item to maintain the distance with the anchors point + auto *itemCont = qobject_cast(item); + if (itemCont && itemCont != layout()->placeHolder()) { + maintainItemEdgeAlignment(itemCont, newGeometry, oldGeometry); + // NOTE: do not use positionItemAndAssign here, because we do not want to emit layoutNeedsSaving, to not save after resize + positionItem(itemCont); + assignSpaceImpl(itemCont); + } + } +} + +void GridLayoutManager::resetLayout() +{ + m_grid.clear(); + m_pointsForItem.clear(); + for (auto *item : layout()->childItems()) { + ItemContainer *itemCont = qobject_cast(item); + if (itemCont && itemCont != layout()->placeHolder()) { + // NOTE: do not use positionItemAndAssign here, because we do not want to emit layoutNeedsSaving, to not save after resize + positionItem(itemCont); + assignSpaceImpl(itemCont); + } + } +} + +void GridLayoutManager::resetLayoutFromConfig() +{ + m_grid.clear(); + m_pointsForItem.clear(); + QList missingItems; + + for (auto *item : layout()->childItems()) { + ItemContainer *itemCont = qobject_cast(item); + if (itemCont && itemCont != layout()->placeHolder()) { + if (!restoreItem(itemCont)) { + missingItems << itemCont; + } + } + } + + for (auto *item : missingItems) { + // NOTE: do not use positionItemAndAssign here, because we do not want to emit layoutNeedsSaving, to not save after resize + positionItem(item); + assignSpaceImpl(item); + } +} + +bool GridLayoutManager::restoreItem(ItemContainer *item) +{ + auto it = m_parsedConfig.find(item->key()); + + if (it != m_parsedConfig.end()) { + // Actual restore + item->setPosition(QPointF(it.value().x, it.value().y)); + item->setSize(QSizeF(it.value().width, it.value().height)); + item->setRotation(it.value().rotation); + + // NOTE: do not use positionItemAndAssign here, because we do not want to emit layoutNeedsSaving, to not save after resize + // If size is empty the layout is not in a valid state and probably startup is not completed yet + if (!layout()->size().isEmpty()) { + releaseSpaceImpl(item); + positionItem(item); + assignSpaceImpl(item); + } + + return true; + } + + return false; +} + +bool GridLayoutManager::isRectAvailable(const QRectF &rect) +{ + //TODO: define directions in which it can grow + if (rect.x() < 0 || rect.y() < 0 || rect.x() + rect.width() > layout()->width() || rect.y() + rect.height() > layout()->height()) { + return false; + } + + const QRect cellItemGeom = cellBasedGeometry(rect); + + for (int row = cellItemGeom.top(); row <= cellItemGeom.bottom(); ++row) { + for (int column = cellItemGeom.left(); column <= cellItemGeom.right(); ++column) { + if (!isCellAvailable(QPair(row, column))) { + return false; + } + } + } + return true; +} + +bool GridLayoutManager::assignSpaceImpl(ItemContainer *item) +{ + // Don't emit extra layoutneedssaving signals + releaseSpaceImpl(item); + if (!isRectAvailable(itemGeometry(item))) { + qWarning()<<"Trying to take space not available" << item; + return false; + } + + const QRect cellItemGeom = cellBasedGeometry(itemGeometry(item)); + + for (int row = cellItemGeom.top(); row <= cellItemGeom.bottom(); ++row) { + for (int column = cellItemGeom.left(); column <= cellItemGeom.right(); ++column) { + QPair cell(row, column); + m_grid.insert(cell, item); + m_pointsForItem[item].insert(cell); + } + } + + // Reorder items tab order + for (auto *i2 : layout()->childItems()) { + ItemContainer *item2 = qobject_cast(i2); + if (item2 && item2->parentItem() == item->parentItem() + && item != item2 && item2 != layout()->placeHolder() + && item->y() < item2->y() + item2->height() + && item->x() <= item2->x()) { + item->stackBefore(item2); + break; + } + } + + if (item->layoutAttached()) { + connect(item, &ItemContainer::sizeHintsChanged, this, [this, item]() { + adjustToItemSizeHints(item); + }); + } + + return true; +} + +void GridLayoutManager::releaseSpaceImpl(ItemContainer *item) +{ + auto it = m_pointsForItem.find(item); + + if (it == m_pointsForItem.end()) { + return; + } + + for (const auto &point : it.value()) { + m_grid.remove(point); + } + + m_pointsForItem.erase(it); + + disconnect(item, &ItemContainer::sizeHintsChanged, this, nullptr); +} + +int GridLayoutManager::rows() const +{ + return layout()->height() / cellSize().height(); +} + +int GridLayoutManager::columns() const +{ + return layout()->width() / cellSize().width(); +} + +void GridLayoutManager::adjustToItemSizeHints(ItemContainer *item) +{ + if (!item->layoutAttached() || item->editMode()) { + return; + } + + bool changed = false; + + // Minimum + const qreal newMinimumHeight = item->layoutAttached()->property("minimumHeight").toReal(); + const qreal newMinimumWidth = item->layoutAttached()->property("minimumWidth").toReal(); + + if (newMinimumHeight > item->height()) { + item->setHeight(newMinimumHeight); + changed = true; + } + if (newMinimumWidth > item->width()) { + item->setWidth(newMinimumWidth); + changed = true; + } + + // Preferred + const qreal newPreferredHeight = item->layoutAttached()->property("preferredHeight").toReal(); + const qreal newPreferredWidth = item->layoutAttached()->property("preferredWidth").toReal(); + + if (newPreferredHeight > item->height()) { + item->setHeight(layout()->cellHeight() * ceil(newPreferredHeight / layout()->cellHeight())); + changed = true; + } + if (newPreferredWidth > item->width()) { + item->setWidth(layout()->cellWidth() * ceil(newPreferredWidth / layout()->cellWidth())); + changed = true; + } + + /*// Maximum : IGNORE? + const qreal newMaximumHeight = item->layoutAttached()->property("preferredHeight").toReal(); + const qreal newMaximumWidth = item->layoutAttached()->property("preferredWidth").toReal(); + + if (newMaximumHeight > 0 && newMaximumHeight < height()) { + item->setHeight(newMaximumHeight); + changed = true; + } + if (newMaximumHeight > 0 && newMaximumWidth < width()) { + item->setWidth(newMaximumWidth); + changed = true; + }*/ + + // Relayout if anything changed + if (changed && itemIsManaged(item)) { + releaseSpace(item); + positionItem(item); + } +} + +QRect GridLayoutManager::cellBasedGeometry(const QRectF &geom) const +{ + return QRect( + round(qBound(0.0, geom.x(), layout()->width() - geom.width()) / cellSize().width()), + round(qBound(0.0, geom.y(), layout()->height() - geom.height()) / cellSize().height()), + round((qreal)geom.width() / cellSize().width()), + round((qreal)geom.height() / cellSize().height()) + ); +} + +QRect GridLayoutManager::cellBasedBoundingGeometry(const QRectF &geom) const +{ + return QRect( + floor(qBound(0.0, geom.x(), layout()->width() - geom.width()) / cellSize().width()), + floor(qBound(0.0, geom.y(), layout()->height() - geom.height()) / cellSize().height()), + ceil((qreal)geom.width() / cellSize().width()), + ceil((qreal)geom.height() / cellSize().height()) + ); +} + +bool GridLayoutManager::isOutOfBounds(const QPair &cell) const +{ + return cell.first < 0 + || cell.second < 0 + || cell.first >= rows() + || cell.second >= columns(); +} + +bool GridLayoutManager::isCellAvailable(const QPair &cell) const +{ + return !isOutOfBounds(cell) && !m_grid.contains(cell); +} + +QRectF GridLayoutManager::itemGeometry(QQuickItem *item) const +{ + return QRectF(item->x(), item->y(), item->width(), item->height()); +} + +QPair GridLayoutManager::nextCell(const QPair &cell, AppletsLayout::PreferredLayoutDirection direction) const +{ + QPair nCell = cell; + + switch (direction) { + case AppletsLayout::AppletsLayout::BottomToTop: + --nCell.first; + break; + case AppletsLayout::AppletsLayout::TopToBottom: + ++nCell.first; + break; + case AppletsLayout::AppletsLayout::RightToLeft: + --nCell.second; + break; + case AppletsLayout::AppletsLayout::LeftToRight: + default: + ++nCell.second; + break; + } + + return nCell; +} + +QPair GridLayoutManager::nextAvailableCell(const QPair &cell, AppletsLayout::PreferredLayoutDirection direction) const +{ + QPair nCell = cell; + while (!isOutOfBounds(nCell)) { + nCell = nextCell(nCell, direction); + + if (isOutOfBounds(nCell)) { + switch (direction) { + case AppletsLayout::AppletsLayout::BottomToTop: + nCell.first = rows() - 1; + --nCell.second; + break; + case AppletsLayout::AppletsLayout::TopToBottom: + nCell.first = 0; + ++nCell.second; + break; + case AppletsLayout::AppletsLayout::RightToLeft: + --nCell.first; + nCell.second = columns() - 1; + break; + case AppletsLayout::AppletsLayout::LeftToRight: + default: + ++nCell.first; + nCell.second = 0; + break; + } + } + + if (isCellAvailable(nCell)) { + return nCell; + } + } + + return QPair(-1, -1); +} + +QPair GridLayoutManager::nextTakenCell(const QPair &cell, AppletsLayout::PreferredLayoutDirection direction) const +{ + QPair nCell = cell; + while (!isOutOfBounds(nCell)) { + nCell = nextCell(nCell, direction); + + if (isOutOfBounds(nCell)) { + switch (direction) { + case AppletsLayout::AppletsLayout::BottomToTop: + nCell.first = rows() - 1; + --nCell.second; + break; + case AppletsLayout::AppletsLayout::TopToBottom: + nCell.first = 0; + ++nCell.second; + break; + case AppletsLayout::AppletsLayout::RightToLeft: + --nCell.first; + nCell.second = columns() - 1; + break; + case AppletsLayout::AppletsLayout::LeftToRight: + default: + ++nCell.first; + nCell.second = 0; + break; + } + } + + if (!isCellAvailable(nCell)) { + return nCell; + } + } + + return QPair(-1, -1); +} + +int GridLayoutManager::freeSpaceInDirection(const QPair &cell, AppletsLayout::PreferredLayoutDirection direction) const +{ + QPair nCell = cell; + + int avail = 0; + + while (isCellAvailable(nCell)) { + ++avail; + nCell = nextCell(nCell, direction); + } + + return avail; +} + +QRectF GridLayoutManager::nextAvailableSpace(ItemContainer *item, const QSizeF &minimumSize, AppletsLayout::PreferredLayoutDirection direction) const +{ + // The mionimum size in grid units + const QSize minimumGridSize( + ceil((qreal)minimumSize.width() / cellSize().width()), + ceil((qreal)minimumSize.height() / cellSize().height()) + ); + + QRect itemCellGeom = cellBasedGeometry(itemGeometry(item)); + itemCellGeom.setWidth(qMax(itemCellGeom.width(), minimumGridSize.width())); + itemCellGeom.setHeight(qMax(itemCellGeom.height(), minimumGridSize.height())); + + QSize partialSize; + + QPair cell(itemCellGeom.y(), itemCellGeom.x()); + if (direction == AppletsLayout::AppletsLayout::RightToLeft) { + cell.second += itemCellGeom.width(); + } else if (direction == AppletsLayout::AppletsLayout::BottomToTop) { + cell.first += itemCellGeom.height(); + } + + if (!isCellAvailable(cell)) { + cell = nextAvailableCell(cell, direction); + } + + while (!isOutOfBounds(cell)) { + + if (direction == AppletsLayout::LeftToRight || direction == AppletsLayout::RightToLeft) { + partialSize = QSize(INT_MAX, 0); + + int currentRow = cell.first; + for (; currentRow < cell.first + itemCellGeom.height(); ++currentRow) { + + const int freeRow = freeSpaceInDirection(QPair(currentRow, cell.second), direction); + + partialSize.setWidth(qMin(partialSize.width(), freeRow)); + + if (freeRow > 0) { + partialSize.setHeight(partialSize.height() + 1); + } else if (partialSize.height() < minimumGridSize.height()) { + break; + } + + if (partialSize.width() >= itemCellGeom.width() + && partialSize.height() >= itemCellGeom.height()) { + break; + } else if (partialSize.width() < minimumGridSize.width()) { + break; + } + } + + if (partialSize.width() >= minimumGridSize.width() + && partialSize.height() >= minimumGridSize.height()) { + + const int width = qMin(itemCellGeom.width(), partialSize.width()) * cellSize().width(); + const int height = qMin(itemCellGeom.height(), partialSize.height()) * cellSize().height(); + + if (direction == AppletsLayout::RightToLeft) { + return QRectF((cell.second + 1) * cellSize().width() - width, + cell.first * cellSize().height(), + width, height); + // AppletsLayout::LeftToRight + } else { + return QRectF(cell.second * cellSize().width(), + cell.first * cellSize().height(), + width, height); + } + } else { + cell = nextAvailableCell(nextTakenCell(cell, direction), direction); + } + + } else if (direction == AppletsLayout::TopToBottom || direction == AppletsLayout::BottomToTop) { + partialSize = QSize(0, INT_MAX); + + int currentColumn = cell.second; + for (; currentColumn < cell.second + itemCellGeom.width(); ++currentColumn) { + + const int freeColumn = freeSpaceInDirection(QPair(cell.first, currentColumn), direction); + + partialSize.setHeight(qMin(partialSize.height(), freeColumn)); + + if (freeColumn > 0) { + partialSize.setWidth(partialSize.width() + 1); + } else if (partialSize.width() < minimumGridSize.width()) { + break; + } + + if (partialSize.width() >= itemCellGeom.width() + && partialSize.height() >= itemCellGeom.height()) { + break; + } else if (partialSize.height() < minimumGridSize.height()) { + break; + } + } + + if (partialSize.width() >= minimumGridSize.width() + && partialSize.height() >= minimumGridSize.height()) { + + const int width = qMin(itemCellGeom.width(), partialSize.width()) * cellSize().width(); + const int height = qMin(itemCellGeom.height(), partialSize.height()) * cellSize().height(); + + if (direction == AppletsLayout::BottomToTop) { + return QRectF(cell.second * cellSize().width(), + (cell.first + 1) * cellSize().height() - height, + width, height); + // AppletsLayout::TopToBottom: + } else { + return QRectF(cell.second * cellSize().width(), + cell.first * cellSize().height(), + width, height); + } + } else { + cell = nextAvailableCell(nextTakenCell(cell, direction), direction); + } + } + } + + //We didn't manage to find layout space, return invalid geometry + return QRectF(); +} + + +#include "moc_gridlayoutmanager.cpp" diff --git a/components/containmentlayoutmanager/itemcontainer.h b/components/containmentlayoutmanager/itemcontainer.h new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/itemcontainer.h @@ -0,0 +1,245 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#pragma once + +#include +#include +#include + +#include "appletslayout.h" + +class QTimer; + +class ConfigOverlay; + +class ItemContainer: public QQuickItem +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(AppletsLayout *layout READ layout NOTIFY layoutChanged) + //TODO: make it unchangeable? probably not + Q_PROPERTY(QString key READ key WRITE setKey NOTIFY keyChanged) + Q_PROPERTY(ItemContainer::EditModeCondition editModeCondition READ editModeCondition WRITE setEditModeCondition NOTIFY editModeConditionChanged) + Q_PROPERTY(bool editMode READ editMode WRITE setEditMode NOTIFY editModeChanged) + Q_PROPERTY(bool dragActive READ dragActive NOTIFY dragActiveChanged) + Q_PROPERTY(AppletsLayout::PreferredLayoutDirection preferredLayoutDirection READ preferredLayoutDirection WRITE setPreferredLayoutDirection NOTIFY preferredLayoutDirectionChanged) + + Q_PROPERTY(QQmlComponent *configOverlayComponent READ configOverlayComponent WRITE setConfigOverlayComponent NOTIFY configOverlayComponentChanged) + Q_PROPERTY(bool configOverlayVisible READ configOverlayVisible WRITE setConfigOverlayVisible NOTIFY configOverlayVisibleChanged) + Q_PROPERTY(QQuickItem *configOverlayItem READ configOverlayItem NOTIFY configOverlayItemChanged) + + /** + * Initial size this container asks to have upon creation. only positive values are considered + */ + Q_PROPERTY(QSizeF initialSize READ initialSize WRITE setInitialSize NOTIFY initialSizeChanged) + // From there mostly a clone of QQC2 Control + Q_PROPERTY(QQuickItem *contentItem READ contentItem WRITE setContentItem NOTIFY contentItemChanged) + Q_PROPERTY(QQuickItem *background READ background WRITE setBackground NOTIFY backgroundChanged) + + /** + * Padding adds a space between each edge of the content item and the background item, effectively controlling the size of the content item. + */ + Q_PROPERTY(int leftPadding READ leftPadding WRITE setLeftPadding NOTIFY leftPaddingChanged) + Q_PROPERTY(int rightPadding READ rightPadding WRITE setRightPadding NOTIFY rightPaddingChanged) + Q_PROPERTY(int topPadding READ topPadding WRITE setTopPadding NOTIFY topPaddingChanged) + Q_PROPERTY(int bottomPadding READ bottomPadding WRITE setBottomPadding NOTIFY bottomPaddingChanged) + + /** + * The size of the contents: the size of this item minus the padding + */ + Q_PROPERTY(int contentWidth READ contentWidth NOTIFY contentWidthChanged) + Q_PROPERTY(int contentHeight READ contentHeight NOTIFY contentHeightChanged) + + Q_PROPERTY(QQmlListProperty contentData READ contentData FINAL) + // Q_CLASSINFO("DeferredPropertyNames", "background,contentItem") + Q_CLASSINFO("DefaultProperty", "contentData") + +public: + enum EditModeCondition { + Locked = AppletsLayout::EditModeCondition::Locked, + Manual = AppletsLayout::EditModeCondition::Manual, + AfterPressAndHold = AppletsLayout::EditModeCondition::AfterPressAndHold, + AfterPress, + AfterMouseOver + }; + Q_ENUMS(EditModeCondition) + + ItemContainer(QQuickItem *parent = nullptr); + ~ItemContainer(); + + QQmlListProperty contentData(); + + QString key() const; + void setKey(const QString &id); + + bool editMode() const; + void setEditMode(bool edit); + + bool dragActive() const; + + EditModeCondition editModeCondition() const; + void setEditModeCondition(EditModeCondition condition); + + AppletsLayout::PreferredLayoutDirection preferredLayoutDirection() const; + void setPreferredLayoutDirection(AppletsLayout::PreferredLayoutDirection direction); + + QQmlComponent *configOverlayComponent() const; + void setConfigOverlayComponent(QQmlComponent *component); + + bool configOverlayVisible() const; + void setConfigOverlayVisible(bool visible); + + //TODO: keep this accessible? + ConfigOverlay *configOverlayItem() const; + + QSizeF initialSize() const; + void setInitialSize(const QSizeF &size); + + // Control-like api + QQuickItem *contentItem() const; + void setContentItem(QQuickItem *item); + + QQuickItem *background() const; + void setBackground(QQuickItem *item); + + // Setters and getters for the padding + int leftPadding() const; + void setLeftPadding(int padding); + + int topPadding() const; + void setTopPadding(int padding); + + int rightPadding() const; + void setRightPadding(int padding); + + int bottomPadding() const; + void setBottomPadding(int padding); + + int contentWidth() const; + int contentHeight() const; + + AppletsLayout *layout() const; + + // Not for QML + void setLayout(AppletsLayout *layout); + + QObject *layoutAttached() const {return m_layoutAttached;} + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + //void classBegin() override; + void componentComplete() override; + bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseUngrabEvent() override; + void hoverEnterEvent(QHoverEvent *event) override; + void hoverLeaveEvent(QHoverEvent *event) override; + +Q_SIGNALS: + + /** + * The user manually dragged the ItemContainer around + * @param newPosition new position of the ItemContainer in parent coordinates + * @param dragCenter position in ItemContainer coordinates of the drag hotspot, i.e. where the user pressed the mouse or the + * finger over the ItemContainer + */ + void userDrag(const QPointF &newPosition, const QPointF &dragCenter); + + void dragActiveChanged(); + + /** + * The attached layout object changed some of its size hints + */ + void sizeHintsChanged(); + + //QML property notifiers + void layoutChanged(); + void keyChanged(); + void editModeConditionChanged(); + void editModeChanged(bool editMode); + void preferredLayoutDirectionChanged(); + void configOverlayComponentChanged(); + void configOverlayItemChanged(); + void initialSizeChanged(); + void configOverlayVisibleChanged(bool configOverlayVisile); + + + void backgroundChanged(); + void contentItemChanged(); + void leftPaddingChanged(); + void rightPaddingChanged(); + void topPaddingChanged(); + void bottomPaddingChanged(); + void contentWidthChanged(); + void contentHeightChanged(); + +private: + void syncChildItemsGeometry(const QSizeF &size); + void sendUngrabRecursive(QQuickItem *item); + + //internal accessorts for the contentData QProperty + static void contentData_append(QQmlListProperty *prop, QObject *object); + static int contentData_count(QQmlListProperty *prop); + static QObject *contentData_at(QQmlListProperty *prop, int index); + static void contentData_clear(QQmlListProperty *prop); + + + QPointer m_contentItem; + QPointer m_backgroundItem; + + //Internal implementation detail: this is used to reparent all items to contentItem + QList m_contentData; + + /** + * Padding adds a space between each edge of the content item and the background item, effectively controlling the size of the content item. + */ + int m_leftPadding = 0; + int m_rightPadding = 0; + int m_topPadding = 0; + int m_bottomPadding = 0; + + + QString m_key; + + QPointer m_layout; + QTimer *m_editModeTimer = nullptr; + QTimer *m_closeEditModeTimer = nullptr; + QTimer *m_sizeHintAdjustTimer = nullptr; + QObject *m_layoutAttached = nullptr; + EditModeCondition m_editModeCondition = Manual; + QSizeF m_initialSize; + + QPointer m_configOverlayComponent; + ConfigOverlay *m_configOverlay = nullptr; + + QPointF m_lastMousePosition = QPoint(-1, -1); + QPointF m_mouseDownPosition = QPoint(-1, -1); + AppletsLayout::PreferredLayoutDirection m_preferredLayoutDirection = AppletsLayout::Closest; + bool m_editMode = false; + bool m_mouseDown = false; + bool m_mouseSynthetizedFromTouch = false; + bool m_dragActive = false; +}; + diff --git a/components/containmentlayoutmanager/itemcontainer.cpp b/components/containmentlayoutmanager/itemcontainer.cpp new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/itemcontainer.cpp @@ -0,0 +1,790 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "itemcontainer.h" +#include "configoverlay.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +ItemContainer::ItemContainer(QQuickItem *parent) + : QQuickItem(parent) +{ + setFiltersChildMouseEvents(true); + setFlags(QQuickItem::ItemIsFocusScope); + setActiveFocusOnTab(true); + setAcceptedMouseButtons(Qt::LeftButton); + + setLayout(qobject_cast(parent)); + + m_editModeTimer = new QTimer(this); + m_editModeTimer->setSingleShot(true); + + connect(this, &QQuickItem::parentChanged, this, [this]() { + setLayout(qobject_cast(parentItem())); + }); + + connect(m_editModeTimer, &QTimer::timeout, this, [this]() { + setEditMode(true); + }); + + + setKeepMouseGrab(true); + m_sizeHintAdjustTimer = new QTimer(this); + m_sizeHintAdjustTimer->setSingleShot(true); + m_sizeHintAdjustTimer->setInterval(0); + + connect(m_sizeHintAdjustTimer, &QTimer::timeout, this, &ItemContainer::sizeHintsChanged); +} + +ItemContainer::~ItemContainer() +{ + if (m_contentItem) { + m_contentItem->setEnabled(true); + } +} + +QString ItemContainer::key() const +{ + return m_key; +} + +void ItemContainer::setKey(const QString &key) +{ + if (m_key == key) { + return; + } + + m_key = key; + + emit keyChanged(); +} + +bool ItemContainer::editMode() const +{ + return m_editMode; +} + +bool ItemContainer::dragActive() const +{ + return m_dragActive; +} + +void ItemContainer::setEditMode(bool editMode) +{ + if (m_editMode == editMode) { + return; + } + + if (editMode && editModeCondition() == Locked) { + return; + } + + m_editMode = editMode; + + if (m_editModeCondition != AfterMouseOver || (m_layout && m_layout->editMode())) { + m_contentItem->setEnabled(!editMode); + } + + if (editMode) { + setZ(1); + } else { + setZ(0); + } + + if (m_mouseDown) { + sendUngrabRecursive(m_contentItem); + grabMouse(); + } + + if (m_dragActive != editMode && m_mouseDown) { + m_dragActive = editMode && m_mouseDown; + emit dragActiveChanged(); + } + + setConfigOverlayVisible(editMode); + + emit editModeChanged(editMode); +} + +ItemContainer::EditModeCondition ItemContainer::editModeCondition() const +{ + if (m_layout && m_layout->editModeCondition() == AppletsLayout::Locked) { + return Locked; + } + + return m_editModeCondition; +} + +void ItemContainer::setEditModeCondition(EditModeCondition condition) +{ + if (condition == m_editModeCondition) { + return; + } + + if (condition == Locked) { + setEditMode(false); + } + + m_editModeCondition = condition; + + setAcceptHoverEvents(condition == AfterMouseOver || (m_layout && m_layout->editMode())); + + emit editModeConditionChanged(); +} + +AppletsLayout::PreferredLayoutDirection ItemContainer::preferredLayoutDirection() const +{ + return m_preferredLayoutDirection; +} + +void ItemContainer::setPreferredLayoutDirection(AppletsLayout::PreferredLayoutDirection direction) +{ + if (direction == m_preferredLayoutDirection) { + return; + } + + m_preferredLayoutDirection = direction; + + emit preferredLayoutDirectionChanged(); +} + +void ItemContainer::setLayout(AppletsLayout *layout) +{ + if (m_layout == layout) { + return; + } + + if (m_layout) { + disconnect(m_layout, &AppletsLayout::editModeConditionChanged, this, nullptr); + disconnect(m_layout, &AppletsLayout::editModeChanged, this, nullptr); + + if (m_editMode) { + m_layout->hidePlaceHolder(); + } + } + + m_layout = layout; + + if (!layout) { + emit layoutChanged(); + return; + } + + if (parentItem() != layout) { + setParentItem(layout); + } + + connect(m_layout, &AppletsLayout::editModeConditionChanged, this, [this]() { + if (m_layout->editModeCondition() == AppletsLayout::Locked) { + setEditMode(false); + } + if ((m_layout->editModeCondition() == AppletsLayout::Locked) != + (m_editModeCondition == ItemContainer::Locked)) { + emit editModeConditionChanged(); + } + }); + connect(m_layout, &AppletsLayout::editModeChanged, this, [this]() { + setAcceptHoverEvents(m_editModeCondition == AfterMouseOver || m_layout->editMode()); + }); + emit layoutChanged(); +} + +AppletsLayout *ItemContainer::layout() const +{ + return m_layout; +} + +void ItemContainer::syncChildItemsGeometry(const QSizeF &size) +{ + if (m_contentItem) { + m_contentItem->setPosition(QPointF(m_leftPadding, m_topPadding)); + + m_contentItem->setSize(QSizeF(size.width() - m_leftPadding - m_rightPadding, + size.height() - m_topPadding - m_bottomPadding)); + } + + if (m_backgroundItem) { + m_backgroundItem->setPosition(QPointF(0, 0)); + m_backgroundItem->setSize(size); + } + + if (m_configOverlay) { + m_configOverlay->setPosition(QPointF(0, 0)); + m_configOverlay->setSize(size); + } +} + +QQmlComponent *ItemContainer::configOverlayComponent() const +{ + return m_configOverlayComponent; +} + +void ItemContainer::setConfigOverlayComponent(QQmlComponent *component) +{ + if (component == m_configOverlayComponent) { + return; + } + + m_configOverlayComponent = component; + if (m_configOverlay) { + m_configOverlay->deleteLater(); + m_configOverlay = nullptr; + } + + emit configOverlayComponentChanged(); +} + +ConfigOverlay *ItemContainer::configOverlayItem() const +{ + return m_configOverlay; +} + +QSizeF ItemContainer::initialSize() const +{ + return m_initialSize; +} + +void ItemContainer::setInitialSize(const QSizeF &size) +{ + if (m_initialSize == size) { + return; + } + + m_initialSize = size; + + emit initialSizeChanged(); +} + +bool ItemContainer::configOverlayVisible() const +{ + return m_configOverlay && m_configOverlay->open(); +} + +void ItemContainer::setConfigOverlayVisible(bool visible) +{ + if (!m_configOverlayComponent) { + return; + } + + if (visible == configOverlayVisible()) { + return; + } + + if (visible && !m_configOverlay) { + QQmlContext *context = QQmlEngine::contextForObject(this); + Q_ASSERT(context); + QObject *instance = m_configOverlayComponent->beginCreate(context); + m_configOverlay = qobject_cast(instance); + + if (!m_configOverlay) { + qWarning() << "Error: Applet configOverlay not of ConfigOverlay type"; + if (instance) { + instance->deleteLater(); + } + return; + } + + m_configOverlay->setVisible(false); + m_configOverlay->setItemContainer(this); + m_configOverlay->setParentItem(this); + m_configOverlay->setTouchInteraction(m_mouseSynthetizedFromTouch); + m_configOverlay->setZ(999); + m_configOverlay->setPosition(QPointF(0, 0)); + m_configOverlay->setSize(size()); + + m_configOverlayComponent->completeCreate(); + + connect(m_configOverlay, &ConfigOverlay::openChanged, this, [this]() { + emit configOverlayVisibleChanged(m_configOverlay->open()); + }); + + emit configOverlayItemChanged(); + } + + if (m_configOverlay) { + m_configOverlay->setOpen(visible); + } +} + +void ItemContainer::contentData_append(QQmlListProperty *prop, QObject *object) +{ + ItemContainer *container = static_cast(prop->object); + if (!container) { + return; + } + +// QQuickItem *item = qobject_cast(object); + container->m_contentData.append(object); +} + +int ItemContainer::contentData_count(QQmlListProperty *prop) +{ + ItemContainer *container = static_cast(prop->object); + if (!container) { + return 0; + } + + return container->m_contentData.count(); +} + +QObject *ItemContainer::contentData_at(QQmlListProperty *prop, int index) +{ + ItemContainer *container = static_cast(prop->object); + if (!container) { + return nullptr; + } + + if (index < 0 || index >= container->m_contentData.count()) { + return nullptr; + } + return container->m_contentData.value(index); +} + +void ItemContainer::contentData_clear(QQmlListProperty *prop) +{ + ItemContainer *container = static_cast(prop->object); + if (!container) { + return; + } + + return container->m_contentData.clear(); +} + +QQmlListProperty ItemContainer::contentData() +{ + return QQmlListProperty(this, nullptr, + contentData_append, + contentData_count, + contentData_at, + contentData_clear); +} + +void ItemContainer::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + syncChildItemsGeometry(newGeometry.size()); + QQuickItem::geometryChanged(newGeometry, oldGeometry); + emit contentWidthChanged(); + emit contentHeightChanged(); +} + +void ItemContainer::componentComplete() +{ + if (!m_contentItem) { + //qWarning()<<"Creating default contentItem"; + m_contentItem = new QQuickItem(this); + syncChildItemsGeometry(size()); + } + + for (auto *o : m_contentData) { + QQuickItem *item = qobject_cast(o); + if (item) { + item->setParentItem(m_contentItem); + } + } + + // Search for the Layout attached property + // Qt6: this should become public api + // https://bugreports.qt.io/browse/QTBUG-77103 + for (auto *o : children()) { + if (o->inherits("QQuickLayoutAttached")) { + m_layoutAttached = o; + } + } + + if (m_layoutAttached) { + //NOTE: new syntax cannot be used because we don't have access to the QQuickLayoutAttached class + connect(m_layoutAttached, SIGNAL(minimumHeightChanged()), m_sizeHintAdjustTimer, SLOT(start())); + connect(m_layoutAttached, SIGNAL(minimumWidthChanged()), m_sizeHintAdjustTimer, SLOT(start())); + + connect(m_layoutAttached, SIGNAL(preferredHeightChanged()), m_sizeHintAdjustTimer, SLOT(start())); + connect(m_layoutAttached, SIGNAL(preferredWidthChanged()), m_sizeHintAdjustTimer, SLOT(start())); + + connect(m_layoutAttached, SIGNAL(maximumHeightChanged()), m_sizeHintAdjustTimer, SLOT(start())); + connect(m_layoutAttached, SIGNAL(maximumWidthChanged()), m_sizeHintAdjustTimer, SLOT(start())); + } + QQuickItem::componentComplete(); +} + +void ItemContainer::sendUngrabRecursive(QQuickItem *item) +{ + if (!item || !item->window()) { + return; + } + + for (auto *child : item->childItems()) { + sendUngrabRecursive(child); + } + + QEvent ev(QEvent::UngrabMouse); + + QCoreApplication::sendEvent(item, &ev); +} + +bool ItemContainer::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + // Don't filter the configoverlay + if (item == m_configOverlay + || (m_configOverlay && m_configOverlay->isAncestorOf(item)) + || (!m_editMode && m_editModeCondition == Manual)) { + return QQuickItem::childMouseEventFilter(item, event); + } + + //give more time before closing + if (m_closeEditModeTimer && m_closeEditModeTimer->isActive()) { + m_closeEditModeTimer->start(); + } + if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent *me = static_cast(event); + if (me->button() != Qt::LeftButton && !(me->buttons() & Qt::LeftButton)) { + return QQuickItem::childMouseEventFilter(item, event); + } + forceActiveFocus(Qt::MouseFocusReason); + m_mouseDown = true; + m_mouseSynthetizedFromTouch = me->source() == Qt::MouseEventSynthesizedBySystem || me->source() == Qt::MouseEventSynthesizedByQt; + if (m_configOverlay) { + m_configOverlay->setTouchInteraction(m_mouseSynthetizedFromTouch); + } + + const bool wasEditMode = m_editMode; + if (m_layout && m_layout->editMode()) { + setEditMode(true); + } else if (m_editModeCondition == AfterPressAndHold) { + m_editModeTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval()); + } + m_lastMousePosition = me->windowPos(); + m_mouseDownPosition = me->windowPos(); + + if (m_editMode && !wasEditMode) { + event->accept(); + return true; + } + + } else if (event->type() == QEvent::MouseMove) { + QMouseEvent *me = static_cast(event); + + if (!m_editMode + && QPointF(me->windowPos() - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) { + m_editModeTimer->stop(); + } else if (m_editMode) { + event->accept(); + } + + } else if (event->type() == QEvent::MouseButtonRelease) { + m_editModeTimer->stop(); + m_mouseDown = false; + m_mouseSynthetizedFromTouch = false; + ungrabMouse(); + event->accept(); + m_dragActive = false; + if (m_editMode) { + emit dragActiveChanged(); + } + } + + return QQuickItem::childMouseEventFilter(item, event); +} + +void ItemContainer::mousePressEvent(QMouseEvent *event) +{ + forceActiveFocus(Qt::MouseFocusReason); + + if (!m_editMode && m_editModeCondition == Manual) { + return; + } + + m_mouseDown = true; + m_mouseSynthetizedFromTouch = event->source() == Qt::MouseEventSynthesizedBySystem || event->source() == Qt::MouseEventSynthesizedByQt; + if (m_configOverlay) { + m_configOverlay->setTouchInteraction(m_mouseSynthetizedFromTouch); + } + + if (m_layout && m_layout->editMode()) { + setEditMode(true); + } + + if (m_editMode) { + grabMouse(); + setCursor(Qt::ClosedHandCursor); + m_dragActive = true; + emit dragActiveChanged(); + } else if (m_editModeCondition == AfterPressAndHold) { + m_editModeTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval()); + } + + m_lastMousePosition = event->windowPos(); + m_mouseDownPosition = event->windowPos(); + event->accept(); +} + +void ItemContainer::mouseReleaseEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + + if (!m_layout + || (!m_editMode && m_editModeCondition == Manual)) { + return; + } + + m_mouseDown = false; + m_mouseSynthetizedFromTouch = false; + m_editModeTimer->stop(); + ungrabMouse(); + + if (m_editMode && !m_layout->itemIsManaged(this)) { + m_layout->hidePlaceHolder(); + m_layout->positionItem(this); + } + + m_dragActive = false; + if (m_editMode) { + emit dragActiveChanged(); + setCursor(Qt::OpenHandCursor); + } + event->accept(); +} + +void ItemContainer::mouseMoveEvent(QMouseEvent *event) +{ + if ((event->button() == Qt::NoButton && event->buttons() == Qt::NoButton) + || (!m_editMode && m_editModeCondition == Manual)) { + return; + } + + if (!m_editMode + && QPointF(event->windowPos() - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) { + if (m_editModeCondition == AfterPress) { + setEditMode(true); + } else { + m_editModeTimer->stop(); + } + } + + if (!m_editMode) { + return; + } + + if (m_layout && m_layout->itemIsManaged(this)) { + m_layout->releaseSpace(this); + grabMouse(); + m_dragActive = true; + emit dragActiveChanged(); + + } else { + setPosition(QPointF(x() + event->windowPos().x() - m_lastMousePosition.x(), + y() + event->windowPos().y() - m_lastMousePosition.y())); + + if (m_layout) { + m_layout->showPlaceHolderForItem(this); + } + + emit userDrag(QPointF(x(), y()), event->pos()); + } + m_lastMousePosition = event->windowPos(); + event->accept(); +} + +void ItemContainer::mouseUngrabEvent() +{ + m_mouseDown = false; + m_mouseSynthetizedFromTouch = false; + m_editModeTimer->stop(); + ungrabMouse(); + + if (m_layout && m_editMode && !m_layout->itemIsManaged(this)) { + m_layout->hidePlaceHolder(); + m_layout->positionItem(this); + } + + m_dragActive = false; + if (m_editMode) { + emit dragActiveChanged(); + } +} + +void ItemContainer::hoverEnterEvent(QHoverEvent *event) +{ + Q_UNUSED(event); + + if (m_editModeCondition != AfterMouseOver && !m_layout->editMode()) { + return; + } + + if (m_closeEditModeTimer) { + m_closeEditModeTimer->stop(); + } + + if (m_layout->editMode()) { + setCursor(Qt::OpenHandCursor); + setEditMode(true); + } else { + m_editModeTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval()); + } +} + +void ItemContainer::hoverLeaveEvent(QHoverEvent *event) +{ + Q_UNUSED(event); + + if (m_editModeCondition != AfterMouseOver && !m_layout->editMode()) { + return; + } + + m_editModeTimer->stop(); + if (!m_closeEditModeTimer) { + m_closeEditModeTimer = new QTimer(this); + m_closeEditModeTimer->setSingleShot(true); + m_closeEditModeTimer->setInterval(500); + connect(m_closeEditModeTimer, &QTimer::timeout, this, [this] () { + setEditMode(false); + }); + } + m_closeEditModeTimer->start(); +} + +QQuickItem *ItemContainer::contentItem() const +{ + return m_contentItem; +} + +void ItemContainer::setContentItem(QQuickItem *item) +{ + if (m_contentItem == item) { + return; + } + + m_contentItem = item; + item->setParentItem(this); + m_contentItem->setPosition(QPointF(m_leftPadding, m_topPadding)); + + m_contentItem->setSize(QSizeF(width() - m_leftPadding - m_rightPadding, + height() - m_topPadding - m_bottomPadding)); + + emit contentItemChanged(); +} + +QQuickItem *ItemContainer::background() const +{ + return m_backgroundItem; +} + +void ItemContainer::setBackground(QQuickItem *item) +{ + if (m_backgroundItem == item) { + return; + } + + m_backgroundItem = item; + m_backgroundItem->setParentItem(this); + m_backgroundItem->setPosition(QPointF(0, 0)); + m_backgroundItem->setSize(size()); + + emit backgroundChanged(); +} + +int ItemContainer::leftPadding() const +{ + return m_leftPadding; +} + +void ItemContainer::setLeftPadding(int padding) +{ + if (m_leftPadding == padding) { + return; + } + + m_leftPadding = padding; + syncChildItemsGeometry(size()); + emit leftPaddingChanged(); + emit contentWidthChanged(); +} + + +int ItemContainer::topPadding() const +{ + return m_topPadding; +} + +void ItemContainer::setTopPadding(int padding) +{ + if (m_topPadding == padding) { + return; + } + + m_topPadding = padding; + syncChildItemsGeometry(size()); + emit topPaddingChanged(); + emit contentHeightChanged(); +} + + +int ItemContainer::rightPadding() const +{ + return m_rightPadding; +} + +void ItemContainer::setRightPadding(int padding) +{ + if (m_rightPadding == padding) { + return; + } + + m_rightPadding = padding; + syncChildItemsGeometry(size()); + emit rightPaddingChanged(); + emit contentWidthChanged(); +} + + +int ItemContainer::bottomPadding() const +{ + return m_bottomPadding; +} + +void ItemContainer::setBottomPadding(int padding) +{ + if (m_bottomPadding == padding) { + return; + } + + m_bottomPadding = padding; + syncChildItemsGeometry(size()); + emit bottomPaddingChanged(); + emit contentHeightChanged(); +} + +int ItemContainer::contentWidth() const +{ + return width() - m_leftPadding - m_rightPadding; +} + +int ItemContainer::contentHeight() const +{ + return height() - m_topPadding - m_bottomPadding; +} + +#include "moc_itemcontainer.cpp" diff --git a/components/containmentlayoutmanager/qml/BasicAppletContainer.qml b/components/containmentlayoutmanager/qml/BasicAppletContainer.qml new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/qml/BasicAppletContainer.qml @@ -0,0 +1,142 @@ +/* + * Copyright 2019 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.2 +import QtGraphicalEffects 1.0 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 3.0 as PlasmaComponents +import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager +import org.kde.kirigami 2.11 as Kirigami + +ContainmentLayoutManager.AppletContainer { + id: appletContainer + editModeCondition: plasmoid.immutable + ? ContainmentLayoutManager.ItemContainer.Manual + : ContainmentLayoutManager.ItemContainer.AfterPressAndHold + + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: (contentItem.effectiveBackgroundHints & PlasmaCore.Types.ShadowBackground) + && !(contentItem.effectiveBackgroundHints & PlasmaCore.Types.StandardBackground) + && !(contentItem.effectiveBackgroundHints & PlasmaCore.Types.TranslucentBackground) + ? Kirigami.Theme.Complementary + : Kirigami.Theme.Window + + PlasmaCore.ColorScope.inherit: false + PlasmaCore.ColorScope.colorGroup: Kirigami.Theme.colorSet == Kirigami.Theme.Complementary + ? PlasmaCore.Theme.ComplementaryColorGroup + : PlasmaCore.Theme.NormalColorGroup + + onFocusChanged: { + if (!focus) { + editMode = false; + } + } + Layout.minimumWidth: { + if (!applet) { + return leftPadding + rightPadding; + } + + if (applet.preferredRepresentation != applet.fullRepresentation + && applet.compactRepresentationItem + ) { + return applet.compactRepresentationItem.Layout.minimumWidth + leftPadding + rightPadding; + } else { + return applet.Layout.minimumWidth + leftPadding + rightPadding; + } + } + Layout.minimumHeight: { + if (!applet) { + return topPadding + bottomPadding; + } + + if (applet.preferredRepresentation != applet.fullRepresentation + && applet.compactRepresentationItem + ) { + return applet.compactRepresentationItem.Layout.minimumHeight + topPadding + bottomPadding; + } else { + return applet.Layout.minimumHeight + topPadding + bottomPadding; + } + } + + Layout.preferredWidth: Math.max(applet.Layout.minimumWidth, applet.Layout.preferredWidth) + Layout.preferredHeight: Math.max(applet.Layout.minimumHeight, applet.Layout.preferredHeight) + + Layout.maximumWidth: applet.Layout.maximumWidth + Layout.maximumHeight: applet.Layout.maximumHeight + + leftPadding: background.margins.left + topPadding: background.margins.top + rightPadding: background.margins.right + bottomPadding: background.margins.bottom + + initialSize.width: applet.switchWidth + leftPadding + rightPadding + initialSize.height: applet.switchHeight + topPadding + bottomPadding + + background: PlasmaCore.FrameSvgItem { + imagePath: { + if (!contentItem) { + return ""; + } + if (contentItem.effectiveBackgroundHints & PlasmaCore.Types.TranslucentBackground) { + return "widgets/translucentbackground"; + } else if (contentItem.effectiveBackgroundHints & PlasmaCore.Types.StandardBackground) { + return "widgets/background"; + } else { + return ""; + } + } + DropShadow { + anchors { + fill: parent + leftMargin: appletContainer.leftPadding + topMargin: appletContainer.topPadding + rightMargin: appletContainer.rightPadding + bottomMargin: appletContainer.bottomPadding + } + z: -1 + horizontalOffset: 0 + verticalOffset: 1 + + radius: 4 + samples: 9 + spread: 0.35 + + color: Qt.rgba(0, 0, 0, 0.5) + opacity: 1 + + source: contentItem && contentItem.effectiveBackgroundHints & PlasmaCore.Types.ShadowBackground ? contentItem : null + visible: source != null + } + } + + busyIndicatorComponent: PlasmaComponents.BusyIndicator { + anchors.centerIn: parent + visible: applet.busy + running: visible + } + configurationRequiredComponent: PlasmaComponents.Button { + anchors.centerIn: parent + text: i18n("Configure...") + visible: applet.configurationRequired + onClicked: applet.action("configure").trigger(); + } +} diff --git a/components/containmentlayoutmanager/qml/ConfigOverlayWithHandles.qml b/components/containmentlayoutmanager/qml/ConfigOverlayWithHandles.qml new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/qml/ConfigOverlayWithHandles.qml @@ -0,0 +1,165 @@ +/* + * Copyright 2019 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 3.0 as PlasmaComponents + +import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager + +import "private" + +ContainmentLayoutManager.ConfigOverlay { + id: overlay + + opacity: open + Behavior on opacity { + OpacityAnimator { + duration: units.longDuration + easing.type: Easing.InOutQuad + } + } + + MultiPointTouchArea { + anchors.fill: parent + property real previousMinX + property real previousMinY + property real previousMaxX + property real previousMaxY + property bool pinching: false + mouseEnabled: false + maximumTouchPoints: 2 + touchPoints: [ + TouchPoint { id: point1 }, + TouchPoint { id: point2 } + ] + + onPressed: { + overlay.itemContainer.layout.releaseSpace(overlay.itemContainer); + previousMinX = point1.sceneX; + previousMinY = point1.sceneY; + } + + onUpdated: { + var minX; + var minY; + var maxX; + var maxY; + + if (point1.pressed && point2.pressed) { + minX = Math.min(point1.sceneX, point2.sceneX); + minY = Math.min(point1.sceneY, point2.sceneY); + + maxX = Math.max(point1.sceneX, point2.sceneX); + maxY = Math.max(point1.sceneY, point2.sceneY); + } else { + minX = point1.pressed ? point1.sceneX : point2.sceneX; + minY = point1.pressed ? point1.sceneY : point2.sceneY; + maxX = -1; + maxY = -1; + } + + if (pinching == (point1.pressed && point2.pressed)) { + overlay.itemContainer.x += minX - previousMinX; + overlay.itemContainer.y += minY - previousMinY; + + if (pinching) { + overlay.itemContainer.width += maxX - previousMaxX + previousMinX - minX; + overlay.itemContainer.height += maxY - previousMaxY + previousMinY - minY; + } + overlay.itemContainer.layout.showPlaceHolderForItem(overlay.itemContainer); + } + + pinching = point1.pressed && point2.pressed + previousMinX = minX; + previousMinY = minY; + previousMaxX = maxX; + previousMaxY = maxY; + } + onReleased: { + if (point1.pressed || point2.pressed) { + return; + } + overlay.itemContainer.layout.positionItem(overlay.itemContainer); + overlay.itemContainer.layout.hidePlaceHolder(); + pinching = false + } + onCanceled: released() + } + + BasicResizeHandle { + resizeCorner: ContainmentLayoutManager.ResizeHandle.TopLeft + anchors { + horizontalCenter: parent.left + verticalCenter: parent.top + } + } + BasicResizeHandle { + resizeCorner: ContainmentLayoutManager.ResizeHandle.Left + anchors { + horizontalCenter: parent.left + verticalCenter: parent.verticalCenter + } + } + BasicResizeHandle { + resizeCorner: ContainmentLayoutManager.ResizeHandle.BottomLeft + anchors { + horizontalCenter: parent.left + verticalCenter: parent.bottom + } + } + BasicResizeHandle { + resizeCorner: ContainmentLayoutManager.ResizeHandle.Bottom + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.bottom + } + } + BasicResizeHandle { + resizeCorner: ContainmentLayoutManager.ResizeHandle.BottomRight + anchors { + horizontalCenter: parent.right + verticalCenter: parent.bottom + } + } + BasicResizeHandle { + resizeCorner: ContainmentLayoutManager.ResizeHandle.Right + anchors { + horizontalCenter: parent.right + verticalCenter: parent.verticalCenter + } + } + BasicResizeHandle { + resizeCorner: ContainmentLayoutManager.ResizeHandle.TopRight + anchors { + horizontalCenter: parent.right + verticalCenter: parent.top + } + } + BasicResizeHandle { + resizeCorner: ContainmentLayoutManager.ResizeHandle.Top + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.top + } + } +} + diff --git a/components/containmentlayoutmanager/qml/PlaceHolder.qml b/components/containmentlayoutmanager/qml/PlaceHolder.qml new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/qml/PlaceHolder.qml @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.12 + +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager + +ContainmentLayoutManager.ItemContainer { + enabled: false + PlasmaCore.FrameSvgItem { + anchors.fill:parent + imagePath: "widgets/viewitem" + prefix: "hover" + opacity: 0.5 + } + Behavior on opacity { + NumberAnimation { + duration: units.longDuration + easing.type: Easing.InOutQuad + } + } +} diff --git a/components/containmentlayoutmanager/qml/private/BasicResizeHandle.qml b/components/containmentlayoutmanager/qml/private/BasicResizeHandle.qml new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/qml/private/BasicResizeHandle.qml @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.12 + +import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager + + +ContainmentLayoutManager.ResizeHandle { + width: overlay.touchInteraction ? units.gridUnit * 2 : units.gridUnit + height: width + z: 999 + Rectangle { + color: resizeBlocked ? theme.negativeTextColor : theme.backgroundColor + anchors.fill: parent + radius: width + opacity: 0.6 + } + scale: overlay.open ? 1 : 0 + Behavior on scale { + NumberAnimation { + duration: units.longDuration + easing.type: Easing.InOutQuad + } + } +} + diff --git a/components/containmentlayoutmanager/qml/qmldir b/components/containmentlayoutmanager/qml/qmldir new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/qml/qmldir @@ -0,0 +1,7 @@ +module org.kde.plasma.private.containmentlayoutmanager + +plugin containmentlayoutmanagerplugin +BasicAppletContainer 1.0 BasicAppletContainer.qml +ConfigOverlayWithHandles 1.0 ConfigOverlayWithHandles.qml +PlaceHolder 1.0 PlaceHolder.qml + diff --git a/components/containmentlayoutmanager/resizehandle.h b/components/containmentlayoutmanager/resizehandle.h new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/resizehandle.h @@ -0,0 +1,77 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#pragma once + +#include + +#include "configoverlay.h" + +class ResizeHandle: public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(Corner resizeCorner MEMBER m_resizeCorner NOTIFY resizeCornerChanged) + Q_PROPERTY(bool resizeBlocked READ resizeBlocked NOTIFY resizeBlockedChanged) + +public: + enum Corner { + Left = 0, + TopLeft, + Top, + TopRight, + Right, + BottomRight, + Bottom, + BottomLeft + }; + Q_ENUMS(Corner) + + ResizeHandle(QQuickItem *parent = nullptr); + ~ResizeHandle(); + + bool resizeBlocked() const; + +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + +Q_SIGNALS: + void resizeCornerChanged(); + void resizeBlockedChanged(); + +private: + void setConfigOverlay(ConfigOverlay *configOverlay); + + inline bool resizeLeft() const; + inline bool resizeTop() const; + inline bool resizeRight() const; + inline bool resizeBottom() const; + void setResizeBlocked(bool width, bool height); + + QPointF m_mouseDownPosition; + QRectF m_mouseDownGeometry; + + QPointer m_configOverlay; + Corner m_resizeCorner = Left; + bool m_resizeWidthBlocked = false; + bool m_resizeHeightBlocked = false; +}; + diff --git a/components/containmentlayoutmanager/resizehandle.cpp b/components/containmentlayoutmanager/resizehandle.cpp new file mode 100644 --- /dev/null +++ b/components/containmentlayoutmanager/resizehandle.cpp @@ -0,0 +1,243 @@ +/* + * Copyright 2019 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "resizehandle.h" + +#include +#include + +ResizeHandle::ResizeHandle(QQuickItem *parent) + : QQuickItem(parent) +{ + setAcceptedMouseButtons(Qt::LeftButton); + + QQuickItem *candidate = parent; + while (candidate) { + ConfigOverlay *overlay = qobject_cast(candidate); + if (overlay) { + setConfigOverlay(overlay); + break; + } + + candidate = candidate->parentItem(); + } + + connect(this, &QQuickItem::parentChanged, this, [this]() { + QQuickItem *candidate = parentItem(); + while (candidate) { + ConfigOverlay *overlay = qobject_cast(candidate); + if (overlay) { + setConfigOverlay(overlay); + break; + } + + candidate = candidate->parentItem(); + } + }); + + auto syncCursor = [this] () { + switch (m_resizeCorner) { + case Left: + case Right: + setCursor(QCursor(Qt::SizeHorCursor)); + break; + case Top: + case Bottom: + setCursor(QCursor(Qt::SizeVerCursor)); + break; + case TopLeft: + case BottomRight: + setCursor(QCursor(Qt::SizeFDiagCursor)); + break; + case TopRight: + case BottomLeft: + default: + setCursor(Qt::SizeBDiagCursor); + } + }; + + syncCursor(); + connect(this, &ResizeHandle::resizeCornerChanged, this, syncCursor); +} + +ResizeHandle::~ResizeHandle() +{ +} + +bool ResizeHandle::resizeBlocked() const +{ + return m_resizeWidthBlocked || m_resizeHeightBlocked; +} + +bool ResizeHandle::resizeLeft() const +{ + return m_resizeCorner == Left || m_resizeCorner == TopLeft || m_resizeCorner == BottomLeft; +} + +bool ResizeHandle::resizeTop() const +{ + return m_resizeCorner == Top || m_resizeCorner == TopLeft || m_resizeCorner == TopRight; +} + +bool ResizeHandle::resizeRight() const +{ + return m_resizeCorner == Right || m_resizeCorner == TopRight ||m_resizeCorner == BottomRight; +} + +bool ResizeHandle::resizeBottom() const +{ + return m_resizeCorner == Bottom || m_resizeCorner == BottomLeft || m_resizeCorner == BottomRight; +} + +void ResizeHandle::setResizeBlocked(bool width, bool height) +{ + if (m_resizeWidthBlocked == width && m_resizeHeightBlocked == height) { + return; + } + + m_resizeWidthBlocked = width; + m_resizeHeightBlocked = height; + + emit resizeBlockedChanged(); +} + + +void ResizeHandle::mousePressEvent(QMouseEvent *event) +{ + ItemContainer *itemContainer = m_configOverlay->itemContainer(); + if (!itemContainer) { + return; + } + m_mouseDownPosition = event->windowPos(); + m_mouseDownGeometry = QRectF(itemContainer->x(), itemContainer->y(), itemContainer->width(), itemContainer->height()); + setResizeBlocked(false, false); + event->accept(); +} + +void ResizeHandle::mouseMoveEvent(QMouseEvent *event) +{ + if (!m_configOverlay || !m_configOverlay->itemContainer()) { + return; + } + + ItemContainer *itemContainer = m_configOverlay->itemContainer(); + AppletsLayout *layout = itemContainer->layout(); + + if (!layout) { + return; + } + + layout->releaseSpace(itemContainer); + const QPointF difference = m_mouseDownPosition - event->windowPos(); + + QSizeF minimumSize = QSize(layout->minimumItemWidth(), layout->minimumItemHeight()); + if (itemContainer->layoutAttached()) { + minimumSize.setWidth(qMax(minimumSize.width(), itemContainer->layoutAttached()->property("minimumWidth").toReal())); + minimumSize.setHeight(qMax(minimumSize.height(), itemContainer->layoutAttached()->property("minimumHeight").toReal())); + } + + //Now make minimumSize an integer number of cells + minimumSize.setWidth(ceil(minimumSize.width() / layout->cellWidth()) * layout->cellWidth()); + minimumSize.setHeight(ceil(minimumSize.height() / layout->cellWidth()) * layout->cellHeight()); + + // Horizontal resize + if (resizeLeft()) { + const qreal width = qMax(minimumSize.width(), m_mouseDownGeometry.width() + difference.x()); + const qreal x = m_mouseDownGeometry.x() + (m_mouseDownGeometry.width() - width); + + // -1 to have a bit of margins around + if (layout->isRectAvailable(x - 1, m_mouseDownGeometry.y(), width, m_mouseDownGeometry.height())) { + itemContainer->setX(x); + itemContainer->setWidth(width); + setResizeBlocked(m_mouseDownGeometry.width() + difference.x() < minimumSize.width(), m_resizeHeightBlocked); + } else { + setResizeBlocked(true, m_resizeHeightBlocked); + } + } else if (resizeRight()) { + const qreal width = qMax(minimumSize.width(), m_mouseDownGeometry.width() - difference.x()); + + if (layout->isRectAvailable(m_mouseDownGeometry.x(), m_mouseDownGeometry.y(), width, m_mouseDownGeometry.height())) { + itemContainer->setWidth(width); + setResizeBlocked(m_mouseDownGeometry.width() - difference.x() < minimumSize.width(), m_resizeHeightBlocked); + } else { + setResizeBlocked(true, m_resizeHeightBlocked); + } + } + + // Vertical Resize + if (resizeTop()) { + const qreal height = qMax(minimumSize.height(), m_mouseDownGeometry.height() + difference.y()); + const qreal y = m_mouseDownGeometry.y() + (m_mouseDownGeometry.height() - height); + + // -1 to have a bit of margins around + if (layout->isRectAvailable(m_mouseDownGeometry.x(), y - 1, m_mouseDownGeometry.width(), m_mouseDownGeometry.height())) { + itemContainer->setY(y); + itemContainer->setHeight(height); + setResizeBlocked(m_resizeWidthBlocked, + m_mouseDownGeometry.height() + difference.y() < minimumSize.height()); + } else { + setResizeBlocked(m_resizeWidthBlocked, true); + } + } else if (resizeBottom()) { + const qreal height = qMax(minimumSize.height(), m_mouseDownGeometry.height() - difference.y()); + + if (layout->isRectAvailable(m_mouseDownGeometry.x(), m_mouseDownGeometry.y(), m_mouseDownGeometry.width(), height)) { + itemContainer->setHeight(qMax(height, minimumSize.height())); + setResizeBlocked(m_resizeWidthBlocked, + m_mouseDownGeometry.height() - difference.y() < minimumSize.height()); + } else { + setResizeBlocked(m_resizeWidthBlocked, true); + } + } + + event->accept(); +} + +void ResizeHandle::mouseReleaseEvent(QMouseEvent *event) +{ + if (!m_configOverlay || !m_configOverlay->itemContainer()) { + return; + } + + ItemContainer *itemContainer = m_configOverlay->itemContainer(); + AppletsLayout *layout = itemContainer->layout(); + + if (!layout) { + return; + } + + layout->positionItem(itemContainer); + + event->accept(); + + setResizeBlocked(false, false); + emit resizeBlockedChanged(); +} + +void ResizeHandle::setConfigOverlay(ConfigOverlay *handle) +{ + if (handle == m_configOverlay) { + return; + } + + m_configOverlay = handle; +} + +#include "moc_resizehandle.cpp" diff --git a/components/keyboardlayout/CMakeLists.txt b/components/keyboardlayout/CMakeLists.txt --- a/components/keyboardlayout/CMakeLists.txt +++ b/components/keyboardlayout/CMakeLists.txt @@ -8,6 +8,8 @@ CATEGORY_NAME kde.keyboardlayout DEFAULT_SEVERITY Info) +qt5_add_dbus_interface(keyboardlayoutplugin_SRCS "org.kde.KeyboardLayouts.xml" keyboard_layout_interface) + add_library(keyboardlayoutplugin SHARED ${keyboardlayoutplugin_SRCS}) target_link_libraries(keyboardlayoutplugin Qt5::Core diff --git a/components/keyboardlayout/keyboardlayout.h b/components/keyboardlayout/keyboardlayout.h --- a/components/keyboardlayout/keyboardlayout.h +++ b/components/keyboardlayout/keyboardlayout.h @@ -23,7 +23,7 @@ #include #include -class QDBusInterface; +class OrgKdeKeyboardLayoutsInterface; class QDBusPendingCallWatcher; class KeyboardLayout : public QObject @@ -72,7 +72,7 @@ QStringList mLayouts; QString mCurrentLayout; QString mCurrentLayoutDisplayName; - QDBusInterface *mIface; + OrgKdeKeyboardLayoutsInterface *mIface; }; diff --git a/components/keyboardlayout/keyboardlayout.cpp b/components/keyboardlayout/keyboardlayout.cpp --- a/components/keyboardlayout/keyboardlayout.cpp +++ b/components/keyboardlayout/keyboardlayout.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Daniel Vratil + * Copyright (C) 2019 David Edmundson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,25 +26,26 @@ #include #include "debug.h" +#include "keyboard_layout_interface.h" + KeyboardLayout::KeyboardLayout(QObject* parent) : QObject(parent) , mIface(nullptr) { - mIface = new QDBusInterface(QStringLiteral("org.kde.keyboard"), + mIface = new OrgKdeKeyboardLayoutsInterface(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), - QStringLiteral("org.kde.KeyboardLayouts"), QDBusConnection::sessionBus(), this); if (!mIface->isValid()) { delete mIface; mIface = nullptr; return; } - connect(mIface, SIGNAL(currentLayoutChanged(QString)), - this, SLOT(setCurrentLayout(QString))); - connect(mIface, SIGNAL(layoutListChanged()), - this, SLOT(requestLayoutsList())); + connect(mIface, &OrgKdeKeyboardLayoutsInterface::currentLayoutChanged, + this, &KeyboardLayout::setCurrentLayout); + connect(mIface, &OrgKdeKeyboardLayoutsInterface::layoutListChanged, + this, &KeyboardLayout::requestLayoutsList); requestCurrentLayout(); requestLayoutsList(); @@ -60,7 +62,7 @@ return; } - QDBusPendingCall pendingLayout = mIface->asyncCall(QStringLiteral("getCurrentLayout")); + QDBusPendingReply pendingLayout = mIface->getCurrentLayout(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingLayout, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &KeyboardLayout::onCurrentLayoutReceived); @@ -81,7 +83,7 @@ void KeyboardLayout::requestCurrentLayoutDisplayName() { - QDBusPendingCall pendingDisplayName = mIface->asyncCallWithArgumentList(QStringLiteral("getLayoutDisplayName"), {mCurrentLayout}); + QDBusPendingReply pendingDisplayName = mIface->getLayoutDisplayName(mCurrentLayout); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingDisplayName, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &KeyboardLayout::onCurrentLayoutDisplayNameReceived); } @@ -109,7 +111,7 @@ return; } - QDBusPendingCall pendingLayout = mIface->asyncCall(QStringLiteral("getLayoutsList")); + QDBusPendingReply pendingLayout = mIface->getLayoutsList(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingLayout, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &KeyboardLayout::onLayoutsListReceived); @@ -150,7 +152,7 @@ mCurrentLayout = layout; requestCurrentLayoutDisplayName(); - mIface->asyncCall(QStringLiteral("setLayout"), layout); + mIface->setLayout(layout); Q_EMIT currentLayoutChanged(layout); } diff --git a/components/keyboardlayout/org.kde.KeyboardLayouts.xml b/components/keyboardlayout/org.kde.KeyboardLayouts.xml new file mode 100644 --- /dev/null +++ b/components/keyboardlayout/org.kde.KeyboardLayouts.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/sessionsprivate/sessionsmodel.cpp b/components/sessionsprivate/sessionsmodel.cpp --- a/components/sessionsprivate/sessionsmodel.cpp +++ b/components/sessionsprivate/sessionsmodel.cpp @@ -209,7 +209,7 @@ { auto reply = m_screensaverInterface->GetActive(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); - QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, cb](QDBusPendingCallWatcher *watcher) { + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [ cb](QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { cb(reply.value()); @@ -251,7 +251,7 @@ if (index.row() == m_data.count()) { switch (static_cast(role)) { case Role::RealName: return i18n("New Session"); - case Role::IconName: return QStringLiteral("list-add"); + case Role::IconName: return QStringLiteral("system-switch-user"); case Role::Name: return i18n("New Session"); case Role::DisplayNumber: return 0; //NA case Role::VtNumber: return -1; //an invalid VtNumber - which we'll use to indicate it's to start a new session diff --git a/components/sessionsprivate/sessionsprivateplugin.cpp b/components/sessionsprivate/sessionsprivateplugin.cpp --- a/components/sessionsprivate/sessionsprivateplugin.cpp +++ b/components/sessionsprivate/sessionsprivateplugin.cpp @@ -25,10 +25,12 @@ #include #include "sessionsmodel.h" +#include void SessionsPrivatePlugin::registerTypes(const char *uri) { Q_ASSERT(uri == QLatin1String("org.kde.plasma.private.sessions")); + qmlRegisterType(uri, 2, 0, "SessionManagement"); qmlRegisterType(uri, 2, 0, "SessionsModel"); } diff --git a/components/shellprivate/interactiveconsole/interactiveconsole.cpp b/components/shellprivate/interactiveconsole/interactiveconsole.cpp --- a/components/shellprivate/interactiveconsole/interactiveconsole.cpp +++ b/components/shellprivate/interactiveconsole/interactiveconsole.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -55,7 +56,7 @@ #include //TODO: -// interative help? +// interactive help? static const QString s_autosaveFileName(QStringLiteral("interactiveconsoleautosave.js")); static const QString s_kwinService = QStringLiteral("org.kde.KWin"); @@ -360,9 +361,10 @@ m_job.data()->kill(); } - m_job = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); - connect(m_job.data(), SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(scriptFileDataRecvd(KIO::Job*,QByteArray))); - connect(m_job.data(), &KJob::result, this, &InteractiveConsole::reenableEditor); + auto job = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); + connect(job, &KIO::TransferJob::data, this, &InteractiveConsole::scriptFileDataRecvd); + connect(job, &KJob::result, this, &InteractiveConsole::reenableEditor); + m_job = job; } } @@ -495,9 +497,10 @@ m_job.data()->kill(); } - m_job = KIO::put(url, -1, KIO::HideProgressInfo); - connect(m_job.data(), SIGNAL(dataReq(KIO::Job*,QByteArray&)), this, SLOT(scriptFileDataReq(KIO::Job*,QByteArray&))); - connect(m_job.data(), &KJob::result, this, &InteractiveConsole::reenableEditor); + auto job = KIO::put(url, -1, KIO::HideProgressInfo); + connect(job, &KIO::TransferJob::dataReq, this, &InteractiveConsole::scriptFileDataReq); + connect(job, &KJob::result, this, &InteractiveConsole::reenableEditor); + m_job = job; } } @@ -550,7 +553,7 @@ QTextBlockFormat block = cursor.blockFormat(); block.setLeftMargin(10); cursor.insertBlock(block, format); - QTime t; + QElapsedTimer t; t.start(); if (m_mode == PlasmaConsole) { diff --git a/components/shellprivate/wallpaperplugin/wallpaperplugin.knsrc b/components/shellprivate/wallpaperplugin/wallpaperplugin.knsrc --- a/components/shellprivate/wallpaperplugin/wallpaperplugin.knsrc +++ b/components/shellprivate/wallpaperplugin/wallpaperplugin.knsrc @@ -8,13 +8,16 @@ Name[el]=Πρόσθετα για ταπετσαρίες Name[en_GB]=Wallpaper Plugins Name[es]=Complementos de fondos del escritorio +Name[et]=Taustapildipluginad Name[eu]=Horma-paper pluginak Name[fi]=Taustakuvaliitännäiset Name[fr]=Modules externes de fonds d'écran Name[gl]=Complementos de fondo de escritorio +Name[hu]=Háttérképmodulok Name[id]=Plugin-plugin Wallpaper Name[it]=Estensioni per lo sfondo Name[ko]=배경 그림 플러그인 +Name[lt]=Darbalaukio fono papildiniai Name[nl]=Achtergrond-plug-ins Name[nn]=Tillegg for bakgrunnsbilete Name[pa]=ਵਾਲਪੇਪਰ ਪਲੱਗਇਨਾਂ diff --git a/components/shellprivate/widgetexplorer/kcategorizeditemsviewmodels.cpp b/components/shellprivate/widgetexplorer/kcategorizeditemsviewmodels.cpp --- a/components/shellprivate/widgetexplorer/kcategorizeditemsviewmodels.cpp +++ b/components/shellprivate/widgetexplorer/kcategorizeditemsviewmodels.cpp @@ -96,7 +96,7 @@ { QList newRow; QStandardItem *item = new QStandardItem(caption); - item->setData(qVariantFromValue(filter)); + item->setData(QVariant::fromValue(filter)); if (!icon.isNull()) { item->setIcon(icon); } diff --git a/components/shellprivate/widgetexplorer/plasmaappletitemmodel.cpp b/components/shellprivate/widgetexplorer/plasmaappletitemmodel.cpp --- a/components/shellprivate/widgetexplorer/plasmaappletitemmodel.cpp +++ b/components/shellprivate/widgetexplorer/plasmaappletitemmodel.cpp @@ -252,7 +252,7 @@ void PlasmaAppletItemModel::populateModel(const QStringList &whatChanged) { - if (!whatChanged.isEmpty() && !whatChanged.contains(QStringLiteral("services"))) { + if (!whatChanged.isEmpty() && !whatChanged.contains(QLatin1String("services"))) { return; } diff --git a/components/shellprivate/widgetexplorer/plasmoids.knsrc b/components/shellprivate/widgetexplorer/plasmoids.knsrc --- a/components/shellprivate/widgetexplorer/plasmoids.knsrc +++ b/components/shellprivate/widgetexplorer/plasmoids.knsrc @@ -1,24 +1,27 @@ [KNewStuff3] Name=Plasma Widgets Name[ar]=ودجات بلازما +Name[ast]=Widgets pa Plasma Name[ca]=Estris del Plasma -Name[ca@valencia]=Estris del Plasma +Name[ca@valencia]=Ginys («widgets») de Plasma Name[cs]=Widgety pro Plasmu Name[da]=Plasma-widgets Name[de]=Plasma-Miniprogramme Name[el]=Γραφικά συστατικά Plasma Name[en_GB]=Plasma Widgets Name[es]=Elementos gráficos de Plasma -Name[eu]=Plasmaren trepetak +Name[et]=Plasma vidinad +Name[eu]=Plasmako trepetak Name[fi]=Plasma-sovelmat Name[fr]=Composants graphiques Plasma Name[gl]=Trebellos de Plasma Name[he]=יישומוני Plasma Name[hu]=Plasma widgetek Name[id]=Widget Plasma Name[it]=Oggetti di Plasma Name[ko]=Plasma 위젯 -Name[lt]=„Plasma“ valdikliai +Name[lt]=Plasma valdikliai +Name[lv]=Plasma logdaļas Name[nl]=Plasma widgets Name[nn]=Plasma-element Name[pa]=ਪਲਾਜ਼ਮਾ ਵਿਜੈਟ @@ -33,6 +36,7 @@ Name[sr@ijekavianlatin]=plasma vidžeti Name[sr@latin]=plasma vidžeti Name[sv]=Plasma grafiska komponenter +Name[tg]=Виҷетҳои Плазма Name[tr]=Plasma Gereçleri Name[uk]=Віджети Плазми Name[x-test]=xxPlasma Widgetsxx diff --git a/components/shellprivate/widgetexplorer/widgetexplorer.cpp b/components/shellprivate/widgetexplorer/widgetexplorer.cpp --- a/components/shellprivate/widgetexplorer/widgetexplorer.cpp +++ b/components/shellprivate/widgetexplorer/widgetexplorer.cpp @@ -212,7 +212,7 @@ WidgetAction *action = nullptr; if (KAuthorized::authorize(QStringLiteral("ghns"))) { - action = new WidgetAction(QIcon::fromTheme(QStringLiteral("applications-internet")), + action = new WidgetAction(QIcon::fromTheme(QStringLiteral("internet-services")), i18n("Download New Plasma Widgets"), this); connect(action, &QAction::triggered, this, &WidgetExplorer::downloadWidgets); actionList << action; diff --git a/config-appstream.h.cmake b/config-appstream.h.cmake new file mode 100644 --- /dev/null +++ b/config-appstream.h.cmake @@ -0,0 +1 @@ +#cmakedefine HAVE_APPSTREAMQT 1 diff --git a/containmentactions/applauncher/plasma-containmentactions-applauncher.desktop b/containmentactions/applauncher/plasma-containmentactions-applauncher.desktop --- a/containmentactions/applauncher/plasma-containmentactions-applauncher.desktop +++ b/containmentactions/applauncher/plasma-containmentactions-applauncher.desktop @@ -2,6 +2,7 @@ Name=Application Launcher Name[af]=Programlanseerder Name[ar]=مُطلِق التطبيقات +Name[ast]=Llanzador d'aplicaciones Name[be]=Запуск праграмаў Name[be@latin]=Uklučeńnie aplikacyi Name[bg]=Стартиране на програми @@ -25,7 +26,7 @@ Name[fr]=Lanceur d'application Name[fy]=In applikaashe starter Name[ga]=Tosaitheoir Feidhmchlár -Name[gl]=Iniciador de aplicativos +Name[gl]=Iniciador de aplicacións Name[gu]=કાર્યક્રમ ચલાવનાર Name[he]=משגר יישומים Name[hi]=अनुप्रयोग चालक @@ -43,7 +44,7 @@ Name[kn]=ಅನ್ವಯ ಪ್ರಕ್ಷೇಪಕ (ಲಾಚರ್) Name[ko]=프로그램 실행기 Name[ku]=Deskpêkerê Sepanan -Name[lt]=Programų paleidiklis +Name[lt]=Programų paleidyklė Name[lv]=Programmu palaidējs Name[mai]=अनुप्रयोग चालक Name[mk]=Стартувач на апликации @@ -72,7 +73,6 @@ Name[sv]=Starta program Name[ta]=பயன்பாடு ஏவி Name[te]=అనువర్తనం దించునది -Name[tg]=Оғози барномаҳо Name[th]=ตัวเรียกใช้งานโปรแกรม Name[tr]=Uygulama Başlatıcısı Name[ug]=پروگرامما ئىجرا قىلغۇچ @@ -106,7 +106,7 @@ Comment[fr]=Lanceur simple d'application Comment[fy]=Ienfâldige applikaasje útfierder Comment[ga]=Tosaitheoir simplí feidhmchlár -Comment[gl]=Iniciador simple de aplicativos +Comment[gl]=Iniciador simple de aplicacións Comment[he]=משגר יישומים פשוט Comment[hi]=सरल अनुप्रयोग चालक Comment[hr]=Jednostavan pokretač aplikacija @@ -120,7 +120,7 @@ Comment[km]=កម្មវិធី​ចាប់ផ្ដើម​កម្មវិធី​សាមញ្ញ​ Comment[kn]=ಸರಳ ಅನ್ವಯ ಪ್ರಕ್ಷೇಪಕ (ಲಾಂಚರ್) Comment[ko]=간단한 프로그램 실행기 -Comment[lt]=Paprastas programų paleidiklis +Comment[lt]=Paprasta programų paleidyklė Comment[lv]=Vienkāršs programmu palaidējs Comment[mk]=Едноставен стартувач на апликации Comment[ml]=ലളിതമായ പ്രയോഗവിക്ഷേപിണി @@ -143,7 +143,6 @@ Comment[sr@ijekavianlatin]=Jednostavan pokretač programa Comment[sr@latin]=Jednostavan pokretač programa Comment[sv]=Enkel programstart -Comment[tg]=Иҷрогари барномаҳо Comment[th]=ตัวเรียกใช้งานโปรแกรมพื้นฐาน Comment[tr]=Basit uygulama başlatıcı Comment[ug]=ئاددىي پروگرامما ئىجراچىسى diff --git a/containmentactions/contextmenu/menu.cpp b/containmentactions/contextmenu/menu.cpp --- a/containmentactions/contextmenu/menu.cpp +++ b/containmentactions/contextmenu/menu.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -76,7 +75,7 @@ } else { actions.insert(QStringLiteral("configure shortcuts"), false); m_actionOrder << QStringLiteral("_context") << QStringLiteral("_run_command") << QStringLiteral("add widgets") << QStringLiteral("_add panel") - << QStringLiteral("manage activities") << QStringLiteral("remove") << QStringLiteral("lock widgets") << QStringLiteral("_sep1") + << QStringLiteral("manage activities") << QStringLiteral("remove") << QStringLiteral("lock widgets") << QStringLiteral("edit mode") << QStringLiteral("_sep1") <containmentType() != Plasma::Types::PanelContainment && c->containmentType() != Plasma::Types::CustomPanelContainment)) { - actions << a; + if (name != QLatin1String("lock widgets") || c->corona()->immutability() != Plasma::Types::Mutable) { + actions << a; + } } } } @@ -181,6 +182,10 @@ if (c->corona()) { return c->corona()->actions()->action(QStringLiteral("lock widgets")); } + } else if (name == QLatin1String("edit mode")) { + if (c->corona()) { + return c->corona()->actions()->action(QStringLiteral("edit mode")); + } } else if (name == QLatin1String("manage activities")) { if (c->corona()) { return c->corona()->actions()->action(QStringLiteral("manage activities")); @@ -225,7 +230,7 @@ { // this short delay is due to two issues: // a) KWorkSpace's DBus alls are all synchronous - // b) the destrution of the menu that this action is in is delayed + // b) the destruction of the menu that this action is in is delayed // // (a) leads to the menu hanging out where everyone can see it because // the even loop doesn't get returned to allowing it to close. diff --git a/containmentactions/contextmenu/plasma-containmentactions-contextmenu.desktop b/containmentactions/contextmenu/plasma-containmentactions-contextmenu.desktop --- a/containmentactions/contextmenu/plasma-containmentactions-contextmenu.desktop +++ b/containmentactions/contextmenu/plasma-containmentactions-contextmenu.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Standard Menu Name[ar]=قائمة قياسية +Name[ast]=Menú estándar Name[bg]=Стандартно меню Name[bs]=Standardni meni Name[ca]=Menú estàndard @@ -50,14 +51,14 @@ Name[ro]=Meniu standard Name[ru]=Стандартное контекстное меню Name[si]=සම්මත මෙනුව -Name[sk]=Štandardné menu +Name[sk]=Štandardná ponuka Name[sl]=Običajni meni Name[sr]=стандардни мени Name[sr@ijekavian]=стандардни мени Name[sr@ijekavianlatin]=standardni meni Name[sr@latin]=standardni meni Name[sv]=Standardmeny -Name[tg]=Менюи стандартӣ +Name[tg]=Феҳристи стандартӣ Name[th]=เมนูมาตรฐาน Name[tr]=Standart Menü Name[ug]=ئۆلچەملىك تىزىملىك @@ -81,12 +82,12 @@ Comment[eo]=La menuo videblas per dekstra musklako Comment[es]=El menú que se suele mostrar al hacer clic derecho Comment[et]=Tavaliselt hiire parema nupu klõpsuga avanev menüü -Comment[eu]=Eskuineko botoiaz klik eginda agertu ohi den menua +Comment[eu]=Eskuineko botoiarekin klik egitean agertu ohi den menua Comment[fi]=Valikko, joka tavallisesti näkyy hiiren kakkospainikkeella Comment[fr]=Le menu s'affichant normalement lors d'un clic droit Comment[fy]=It menu dat normaal mei rjochts klik ferskynt Comment[ga]=An roghchlár a thaispeántar tar éis deaschliceáil -Comment[gl]=O menú que se mostra xeralmente ao facer clic dereito +Comment[gl]=O menú que se mostra xeralmente ao premer co botón dereito Comment[he]=התפריט שמופיע בעת לחיצה על הכפתור הימני בעכבר Comment[hr]=Izbornik koji se obično pojavljuje na desni klik Comment[hu]=A jobb egérkattintásra megjelenő menü @@ -99,7 +100,7 @@ Comment[km]=ម៉ឺនុយ​ដែល​តាម​ធម្មតា​បង្ហាញ​នៅ​ពេល​ចុច​កណ្ដុរ​ស្ដាំ Comment[kn]=ಸಾಮಾನ್ಯವಾಗಿ ಮೌಸ್‌ನ ಬಲ ಬದಿಯ ಕ್ಲಿಕ್‌ಗೆ ತೆರೆಯುವ ಪರಿವಿಡಿ Comment[ko]=오른쪽 단추를 눌렀을 때 나타나는 메뉴 -Comment[lt]=Šis meniu paprastai rodomas po pelės dešinio klavišo paspaudimo +Comment[lt]=Meniu, kuris įprastai rodomas spustelėjus dešiniuoju pelės mygtuku Comment[lv]=Izvēlne, kas parasti parādās pēc labā peles klikšķa Comment[mk]=Менито што обично се појавува на десно кликнување Comment[ml]=വലത്-ഞെക്കില്‍ സാധാരണ കാണിയ്ക്കുന്ന മെനു @@ -115,7 +116,7 @@ Comment[ro]=Meniul ce apare de obicei la clic-dreapta Comment[ru]=Меню, которое обычно открывается правой кнопкой мыши Comment[si]=දකුණු-ක්ලික් කිරීමට සාමාන්‍යයෙන් ලැබෙන මෙනුව -Comment[sk]=Menu, ktoré sa zvyčajne zobrazuje po kliknutí pravým tlačidlom myši +Comment[sk]=Ponuka, ktorá sa zvyčajne zobrazuje po kliknutí pravým tlačidlom myši Comment[sl]=Meni, ki se običajno prikaže ob desnem kliku Comment[sr]=Мени који се обично показује на десни клик Comment[sr@ijekavian]=Мени који се обично показује на десни клик diff --git a/containmentactions/paste/plasma-containmentactions-paste.desktop b/containmentactions/paste/plasma-containmentactions-paste.desktop --- a/containmentactions/paste/plasma-containmentactions-paste.desktop +++ b/containmentactions/paste/plasma-containmentactions-paste.desktop @@ -5,7 +5,7 @@ Name[bn]=পেস্ট Name[bs]=Naljepljivanje Name[ca]=Enganxa -Name[ca@valencia]=Enganxa +Name[ca@valencia]=Apega Name[cs]=Vložit Name[csb]=Wlepi Name[da]=Indsæt @@ -36,7 +36,7 @@ Name[km]=បិទភ្ជាប់ Name[kn]=ಅಂಟಿಸು Name[ko]=붙여넣기 -Name[lt]=Įterpti +Name[lt]=Įdėti Name[lv]=Ielīmēt Name[ml]=ഒട്ടിയ്ക്കുക Name[mr]=चिटकवा @@ -58,7 +58,7 @@ Name[sr@ijekavianlatin]=naljepljivanje Name[sr@latin]=nalepljivanje Name[sv]=Klistra in -Name[tg]=Часпондан +Name[tg]=Гузоштан Name[th]=วาง Name[tr]=Yapıştır Name[ug]=چاپلا @@ -74,16 +74,16 @@ Comment[bg]=Създаване на джаджа със съдържанието на системния буфер Comment[bs]=Stvara grafičku kontrolu na osnovu sadržaja klipborda Comment[ca]=Crea un estri a partir del contingut del porta-retalls -Comment[ca@valencia]=Crea un estri a partir del contingut del porta-retalls +Comment[ca@valencia]=Crea un giny («widget») a partir del contingut del porta-retalls Comment[cs]=Vytvoří widget z obsahu schránky Comment[da]=Opretter en widget ud fra indholdet af udklipsholderen Comment[de]=Erzeugt ein Miniprogramm aus dem Inhalt der Zwischenablage Comment[el]=Δημιουργία γραφικού συστατικού από τα περιεχόμενα του προχείρου Comment[en_GB]=Creates a widget from the contents of the clipboard Comment[eo]=Krei fenestraĵon el la enhavo de la tondejo Comment[es]=Crea un elemento gráfico con los contenidos del portapapeles Comment[et]=Vidina loomine lõikepuhvri sisu põhjal -Comment[eu]=Trepeta bat sortzen du arbelaren edukiarekin +Comment[eu]=Trepeta bat sortzen du arbelaren edukitik Comment[fi]=Luo käyttöliittymäelementin leikepöydän sisällöstä Comment[fr]=Crée un composant graphique à partir du contenu du presse-papier Comment[fy]=Makket in widget fan de ynhâld fan in klamboerd @@ -101,7 +101,7 @@ Comment[km]=បង្កើត​ធាតុក្រាហ្វិក​ពី​មាតិកា​របស់​ក្ដារតម្បៀតខ្ទាស់ Comment[kn]=ನಕಲುಫಲಕದಲ್ಲಿರುವ (ಕ್ಲಿಪ್‌ಬೋರ್ಡ್) ವಿಷಯಗಳಿಂದ ಒಂದು ನಿಯಂತ್ರಣಾ ಸಂಪರ್ಕತಟವನ್ನು(ವಿಡ್ಗೆಟ್) ನಿರ್ಮಿಸುತ್ತದೆ Comment[ko]=클립보드 내용을 기반으로 위젯을 만듭니다 -Comment[lt]=Sukuria valdiklį iš laikinosios atmintinės turinio +Comment[lt]=Sukuria valdiklį iš iškarpinės turinio Comment[lv]=Izveido sīkrīku no starpliktuves satura Comment[ml]=ഓര്‍മ്മച്ചെപ്പിലെ ഉള്ളടക്കങ്ങള്‍ കൊണ്ട് ഒരു ഉരുപ്പടിയുണ്ടാക്കുന്നു Comment[mr]=क्लिपबोर्ड मधील मजकूरावरुन विजेट बनवितो diff --git a/containmentactions/switchactivity/plasma-containmentactions-switchactivity.desktop b/containmentactions/switchactivity/plasma-containmentactions-switchactivity.desktop --- a/containmentactions/switchactivity/plasma-containmentactions-switchactivity.desktop +++ b/containmentactions/switchactivity/plasma-containmentactions-switchactivity.desktop @@ -32,7 +32,7 @@ Name[km]=ប្ដូរ​សកម្មភាព Name[kn]=ಚಟುವಟಿಕೆಯನ್ನು ಬದಲಾಯಿಸು Name[ko]=활동 전환 -Name[lt]=Pakeisti veiklą +Name[lt]=Perjungti veiklą Name[lv]=Pārslēgt aktivitāti Name[mk]=Смени активност Name[ml]=പ്രക്രിയ മാറ്റുക @@ -55,7 +55,7 @@ Name[sr@ijekavianlatin]=prebacivanje aktivnosti Name[sr@latin]=prebacivanje aktivnosti Name[sv]=Byt aktivitet -Name[tg]=Мубодилаи амалиёт +Name[tg]=Гузариши фаъолият Name[th]=สลับกิจกรรม Name[tr]=Etkinlik Değiştir Name[ug]=پائالىيەت ئالماشتۇر @@ -98,7 +98,7 @@ Comment[km]=ប្ដូរ​ទៅ​សកម្មភាព​ផ្សេង Comment[kn]=ಬೇರೊಂದು ಚಟುವಟಿಕೆಗೆ ಬದಲಾಯಿಸು Comment[ko]=다른 활동으로 전환합니다 -Comment[lt]=Persijungti į kitą veiklą +Comment[lt]=Perjungti į kitą veiklą Comment[lv]=Pārslēgties uz citu aktivitāti Comment[mk]=Се префрла на друга активност Comment[ml]=മറ്റൊരു പ്രക്രിയിലേയ്ക്കുള്ള മാറ്റം @@ -121,7 +121,7 @@ Comment[sr@ijekavianlatin]=Prebacuje na drugu aktivnost Comment[sr@latin]=Prebacuje na drugu aktivnost Comment[sv]=Byt till en annan aktivitet -Comment[tg]=Мубодила ба дигар амалиёт +Comment[tg]=Гузариш ба фаъолияти дигар Comment[th]=สลับไปยังกิจกรรมอื่น ๆ Comment[tr]=Başka bir etkinliğe geç Comment[ug]=باشقا بىر پائالىيەتكە ئالماشتۇر diff --git a/containmentactions/switchdesktop/plasma-containmentactions-switchdesktop.desktop b/containmentactions/switchdesktop/plasma-containmentactions-switchdesktop.desktop --- a/containmentactions/switchdesktop/plasma-containmentactions-switchdesktop.desktop +++ b/containmentactions/switchdesktop/plasma-containmentactions-switchdesktop.desktop @@ -34,7 +34,7 @@ Name[km]=ប្ដូរ​ផ្ទៃតុ Name[kn]=ಗಣಕತೆರೆಯನ್ನು ಬದಲಾಯಿಸು Name[ko]=데스크톱 전환 -Name[lt]=Pakeisti darbalaukį +Name[lt]=Perjungti darbalaukį Name[lv]=Pārslēgt darbvirsmu Name[mk]=Смени раб. површина Name[ml]=പണിയിടം മാറുക @@ -57,7 +57,7 @@ Name[sr@ijekavianlatin]=prebacivanje površi Name[sr@latin]=prebacivanje površi Name[sv]=Byt skrivbord -Name[tg]=Мубодилаи мизи корӣ +Name[tg]=Гузариши мизи корӣ Name[th]=สลับพื้นที่ทำงาน Name[tr]=Masaüstünü Değiştir Name[ug]=ئۈستەلئۈستى ئالماشتۇر @@ -101,7 +101,7 @@ Comment[km]=ប្ដូរ​ទៅ​ផ្ទៃតុ​និម្មិត​ផ្សេង​ទៀត Comment[kn]=ಮತ್ತೊಂದು ವಾಸ್ತವಪ್ರಾಯ ಗಣಕತೆರೆಗೆ ಬದಲಾಯಿಸು Comment[ko]=다른 가상 데스크톱으로 전환합니다 -Comment[lt]=Pereiti į kitą virtualų darbalaukį +Comment[lt]=Perjungti į kitą virtualų darbalaukį Comment[lv]=Pārslēgties uz citu virtuālo darbvirsmu Comment[mk]=Менување на друга виртуелна раб. површина Comment[ml]=മറ്റൊരു മായാ പണിയിടത്തിലേയ്ക്കു് മാറുക @@ -124,7 +124,7 @@ Comment[sr@ijekavianlatin]=Prebacuje na drugu virtuelnu površ Comment[sr@latin]=Prebacuje na drugu virtuelnu površ Comment[sv]=Byt till ett annat virtuellt skrivbord -Comment[tg]=Мубодилаи мизи кориҳои виртуалӣ +Comment[tg]=Гузариш ба мизи кории маҷозии дигар Comment[th]=สลับไปยังพื้นที่ทำงานเสมือนตัวอื่น ๆ Comment[tr]=Başka bir sanal masaüstüne geç Comment[ug]=باشقا بىر مەۋھۇم ئۈستەلئۈستىگە ئالماشتۇر diff --git a/containmentactions/switchwindow/plasma-containmentactions-switchwindow.desktop b/containmentactions/switchwindow/plasma-containmentactions-switchwindow.desktop --- a/containmentactions/switchwindow/plasma-containmentactions-switchwindow.desktop +++ b/containmentactions/switchwindow/plasma-containmentactions-switchwindow.desktop @@ -32,7 +32,7 @@ Name[km]=ប្ដូរ​បង្អួច Name[kn]=ಕಿಟಿಕಿಯನ್ನು ಬದಲಾಯಿಸು Name[ko]=창 전환 -Name[lt]=Pakeisti langą +Name[lt]=Perjungti langą Name[lv]=Pārslēgt logu Name[mk]=Смени прозорец Name[ml]=ജാലകം മാറുക @@ -55,7 +55,7 @@ Name[sr@ijekavianlatin]=prebacivanje prozora Name[sr@latin]=prebacivanje prozora Name[sv]=Byt fönster -Name[tg]=Мубодилаи тирезаҳо +Name[tg]=Гузариши равзана Name[th]=สลับหน้าต่าง Name[tr]=Pencereyi Değiştir Name[ug]=كۆزنەك ئالماشتۇر @@ -97,7 +97,7 @@ Comment[km]=បង្ហាញ​បញ្ជី​បង្អួច​ដើម្បី​ផ្លាស់ប្ដូរ Comment[kn]=ವಿಂಡೊವನ್ನು ಬದಲಾಯಿಸಲು ವಿಂಡೊಗಳ ಒಂದು ಪಟ್ಟಿಯನ್ನು ತೋರಿಸು Comment[ko]=전환할 수 있는 창 목록을 표시합니다 -Comment[lt]=Rodo langų, į kuriuos galima rodyti, sąrašą +Comment[lt]=Rodyti langų, į kuriuos perjungti, sąrašą Comment[lv]=Pārādīt logu sarakstu uz ko pārslēgties Comment[mk]=Прикажува листа на прозорци во кои може да се префрлите Comment[ml]=മാറാവുന്ന ജാലകങ്ങളടെ പട്ടിക കാണിയ്ക്കുക diff --git a/containmentactions/switchwindow/switch.cpp b/containmentactions/switchwindow/switch.cpp --- a/containmentactions/switchwindow/switch.cpp +++ b/containmentactions/switchwindow/switch.cpp @@ -52,7 +52,7 @@ s_tasksModel->setActivity(s_activityInfo->currentActivity()); s_tasksModel->setFilterByActivity(true); connect(s_activityInfo, &ActivityInfo::currentActivityChanged, - this, [this]() { s_tasksModel->setActivity(s_activityInfo->currentActivity()); }); + this, []() { s_tasksModel->setActivity(s_activityInfo->currentActivity()); }); } } @@ -174,7 +174,7 @@ const QVariant &desktop = desktopIds.at(i); if (desktops.contains(desktop)) { - const QString &name = QStringLiteral("%1: %2").arg(QString::number(i), desktopNames.at(i)); + const QString &name = QStringLiteral("%1: %2").arg(QString::number(i + 1), desktopNames.at(i)); QAction *a = new QAction(name, this); a->setSeparator(true); m_actions << a; @@ -193,7 +193,7 @@ const QVariant &desktop = desktopIds.at(i); if (desktops.contains(desktop)) { - const QString &name = QStringLiteral("%1: %2").arg(QString::number(i), desktopNames.at(i)); + const QString &name = QStringLiteral("%1: %2").arg(QString::number(i + 1), desktopNames.at(i)); QMenu *subMenu = new QMenu(name); subMenu->addActions(desktops.values(desktop)); diff --git a/dataengines/CMakeLists.txt b/dataengines/CMakeLists.txt --- a/dataengines/CMakeLists.txt +++ b/dataengines/CMakeLists.txt @@ -24,7 +24,6 @@ add_subdirectory(time) add_subdirectory(weather) add_subdirectory(statusnotifieritem) -add_subdirectory(share) if(NOT WIN32) add_subdirectory(mouse) diff --git a/dataengines/activities/plasma-dataengine-activities.desktop b/dataengines/activities/plasma-dataengine-activities.desktop --- a/dataengines/activities/plasma-dataengine-activities.desktop +++ b/dataengines/activities/plasma-dataengine-activities.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Activities Engine Name[ar]=محرّك الأنشطة +Name[ast]=Motor d'actividaes Name[bg]=Ядро за дейности Name[bs]=Motor aktivnosti Name[ca]=Motor d'activitats @@ -29,7 +30,7 @@ Name[km]=ម៉ាស៊ីន​សកម្មភាព Name[kn]=ಚಟುವಟಿಕೆ ಎಂಜಿನ್ Name[ko]=활동 엔진 -Name[lt]=Veiklų modulis +Name[lt]=Veiklų variklis Name[lv]=Aktivitāšu dzinējs Name[mr]=कार्यपध्दती इंजिन Name[nb]=Aktivitetsmotor @@ -49,6 +50,7 @@ Name[sr@ijekavianlatin]=motor aktivnosti Name[sr@latin]=motor aktivnosti Name[sv]=Aktivitetsgränssnitt +Name[tg]=Низоми фаъолиятҳо Name[th]=กลไกจัดการกิจกรรม Name[tr]=Etkinlik Motoru Name[ug]=پائالىيەت ماتورى @@ -63,7 +65,7 @@ Comment[bg]=Данни за дейности в Plasma Comment[bs]=Podaci o plazma aktivnostima Comment[ca]=Informació quant a activitats del Plasma -Comment[ca@valencia]=Informació quant a activitats del Plasma +Comment[ca@valencia]=Informació quant a activitats de Plasma Comment[cs]=Informace o Plasma aktivitách. Comment[da]=Information om Plasma-aktiviteter Comment[de]=Informationen über Plasma-Aktivitäten diff --git a/dataengines/applicationjobs/plasma-dataengine-applicationjobs.desktop b/dataengines/applicationjobs/plasma-dataengine-applicationjobs.desktop --- a/dataengines/applicationjobs/plasma-dataengine-applicationjobs.desktop +++ b/dataengines/applicationjobs/plasma-dataengine-applicationjobs.desktop @@ -19,7 +19,7 @@ Name[fr]=Informations sur les tâches d'applications en cours d'exécution Name[fy]=Programma taak ynformaasje Name[ga]=Eolas Faoi Jabanna Feidhmchláir -Name[gl]=Información de tarefas de aplicativo +Name[gl]=Información de tarefas de aplicación Name[gu]=કાર્યક્રમ કાર્ય માહિતી Name[he]=מידע אודות עבודת יישום Name[hi]=अनुप्रयोग कार्य जानकारी @@ -50,7 +50,7 @@ Name[pt]=Informação da Tarefa da Aplicação Name[pt_BR]=Informações da tarefa do aplicativo Name[ro]=Informație despre sarcina aplicației -Name[ru]=Системные уведомления +Name[ru]=Задания в приложениях Name[si]=යෙදුම් කාර්‍යය තොරතුරු Name[sk]=Informácie o aplikácii Name[sl]=Posli programov @@ -60,7 +60,6 @@ Name[sr@latin]=podaci o poslovima programâ̂ Name[sv]=Information om programjobb Name[ta]=பயன்பாட்டு பணி விவரம் -Name[tg]=Иттилооти корҳои барнома Name[th]=ข้อมูลงานของโปรแกรม Name[tr]=Uygulama Görev Bilgileri Name[ug]=پروگرامما خىزمەت ئۇچۇرى @@ -87,12 +86,12 @@ Comment[fi]=Sovellustyöpäivitykset (kuiserver-palvelimen kautta) Comment[fr]=Mise à jours des tâches d'application (via « kuiserver ») Comment[fy]=Applikaasje taak fernijing (mei kuiserver) -Comment[gl]=Actualización de tarefa do aplicativo (mediante kuiserver) +Comment[gl]=Actualización de tarefa da aplicación (mediante kuiserver) Comment[he]=עידכוני עבודת יישום (באמצעות kuiserver) Comment[hr]=Ažuriranja poslova aplikacija (preko kuiservera) Comment[hu]=Feladatfrissítő (a kuiserveren keresztül) Comment[ia]=Actualisationes de travalio de application (via kuiserver) -Comment[id]=Update-an job aplikasi (via kuiserver) +Comment[id]=Pembaruan job aplikasi (via kuiserver) Comment[is]=Uppfærslutilkynningar um verkefni forrita (með kuiserver) Comment[it]=Aggiornamenti sui processi delle applicazioni (con kuiserver) Comment[ja]=アプリケーションジョブ更新 (kuiserver 経由) @@ -114,16 +113,15 @@ Comment[pt]=Actualizações das tarefas da aplicação (através do 'kuiserver') Comment[pt_BR]=Atualizações de tarefas de aplicativos (via kuiserver) Comment[ro]=Actualizări pentru sarcina aplicației (via kuiserver) -Comment[ru]=Обновление заданий приложений (с помощью диспетчера заданий) +Comment[ru]=Обновления о заданиях в приложениях (данные от kuiserver) Comment[si]=යෙදුම් කාර්‍ය යාවත්කාලීන (kuiserver වෙතින්) Comment[sk]=Aktualizácie úloh aplikácií (pomocou kuiserver) Comment[sl]=Obveščanje o poslih programov (prek KUIServer) Comment[sr]=Ажурирања програмских послова (преко КУИ‑сервера) Comment[sr@ijekavian]=Ажурирања програмских послова (преко КУИ‑сервера) Comment[sr@ijekavianlatin]=Ažuriranja programskih poslova (preko KUIServera) Comment[sr@latin]=Ažuriranja programskih poslova (preko KUIServera) Comment[sv]=Uppdateringar av information om programjobb (via kuiserver) -Comment[tg]=Иттилооти амалҳои система (via kuiserver) Comment[th]=ปรับปรุงงานของโปรแกรม (ผ่านทาง kuiserver) Comment[tr]=Uygulama görevi güncellemeleri (kuiserver ile) Comment[ug]=پروگرامما ۋەزىپىسى يېڭىلاندى(kuiserver ئارقىلىق) diff --git a/dataengines/apps/appsengine.cpp b/dataengines/apps/appsengine.cpp --- a/dataengines/apps/appsengine.cpp +++ b/dataengines/apps/appsengine.cpp @@ -35,12 +35,12 @@ void AppsEngine::init() { addGroup(KServiceGroup::root()); - connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), this, SLOT(sycocaChanged(QStringList))); + connect(KSycoca::self(), QOverload::of(&KSycoca::databaseChanged), this, &AppsEngine::sycocaChanged); } void AppsEngine::sycocaChanged(const QStringList &changes) { - if (changes.contains(QStringLiteral("apps")) || changes.contains(QStringLiteral("xdgdata-apps"))) { + if (changes.contains(QLatin1String("apps")) || changes.contains(QLatin1String("xdgdata-apps"))) { removeAllSources(); addGroup(KServiceGroup::root()); } diff --git a/dataengines/apps/plasma-dataengine-apps.desktop b/dataengines/apps/plasma-dataengine-apps.desktop --- a/dataengines/apps/plasma-dataengine-apps.desktop +++ b/dataengines/apps/plasma-dataengine-apps.desktop @@ -19,7 +19,7 @@ Name[fr]=Informations sur l'application Name[fy]=Applikaasje ynformaasje Name[ga]=Eolas faoi Fheidhmchláir -Name[gl]=Información do aplicativo +Name[gl]=Información da aplicación Name[gu]=કાર્યક્રમ માહિતી Name[he]=מידע אודות יישום Name[hi]=अनुप्रयोग जानकारी @@ -82,7 +82,7 @@ Comment[fi]=Tietoja ja kaikkien sovellusten käynnistäminen sovellusvalikossa. Comment[fr]=Informations et démarrage de toutes les applications du menu des applications. Comment[fy]=Ynformaasje en útfiere fan alle applikaasjes yn it app menu. -Comment[gl]=Información e inicio de todos os aplicativos do menú. +Comment[gl]=Información e inicio de todas as aplicacións do menú. Comment[he]=מידע והפעלה של כל היישומים בתפריט היישומים. Comment[hr]=Informacije i pokretanja od svih aplikacija u izborniku aplikacija. Comment[hu]=Az alkalmazásmenü alkalmazásainak indítása és jellemzőinek megjelenítése. @@ -95,7 +95,7 @@ Comment[km]=ព័ត៌មាន និង​ការ​ចាប់ផ្ដើម​កម្មវិធី​ទាំងអស់​នៅ​ក្នុង​ម៉ឺនុយ​កម្មវិធី ។ Comment[kn]=App ಮೆನುವಿನಲ್ಲಿರುವ ಎಲ್ಲಾ ಅನ್ವಯಗಳ ಬಗೆಗಿನ ಮಾಹಿತಿ ಹಾಗು ಪ್ರಕ್ಷೇಪಣೆ(ಲಾಂಚಿಂಗ್). Comment[ko]=프로그램 메뉴의 정보를 보고 실행할 수 있도록 합니다. -Comment[lt]=Programų leistukai ir informacija iš programų meniu. +Comment[lt]=Visų programų informacija ir paleidimas programų meniu. Comment[lv]=Visu programmu informācija un palaišana programmu izvēlnē. Comment[mk]=Информации и стартување на сите апликации од менито за апликации Comment[ml]=ആപ്പ് മെനുവിലെ പ്രയോഗങ്ങളുടെ വിവരവും തുടക്കവും. @@ -111,14 +111,13 @@ Comment[ro]=Informații și lansare de aplicații în meniul aplicațiilor. Comment[ru]=Информация и запуск всех приложений из меню приложений. Comment[si]=යෙදුම් මෙනුවේ ඇති සියළු යෙදුම් ආරම්භ කිරීමේ තොරතුරු -Comment[sk]=Informácie a spúšťanie všetkých aplikácií v menu aplikácie. +Comment[sk]=Informácie a spúšťanie všetkých aplikácií v ponuke aplikácie. Comment[sl]=Podatki o vseh programih v meniju in njihovo zaganjanje Comment[sr]=Подаци о програмима из програмског менија и њихово покретање. Comment[sr@ijekavian]=Подаци о програмима из програмског менија и њихово покретање. Comment[sr@ijekavianlatin]=Podaci o programima iz programskog menija i njihovo pokretanje. Comment[sr@latin]=Podaci o programima iz programskog menija i njihovo pokretanje. Comment[sv]=Information om och start av alla program i programmenyn. -Comment[tg]=Иттилоот ва оғозии ҳамаи барномаҳо аз меню. Comment[th]=ข้อมูลและการเรียกใช้โปรแกรมทั้งหมดที่อยู่ในเมนูโปรแกรม Comment[tr]=Uygulama menüsündeki tüm uygulamalar hakkında bilgiler ve uygulamaları çalıştırma. Comment[ug]=ئۇچۇر كۆرسىتىدۇ ياكى تىزىملىكتىكى ھەممە پروگراممىلارنى ئىجرا قىلىدۇ diff --git a/dataengines/devicenotifications/plasma-dataengine-devicenotifications.desktop b/dataengines/devicenotifications/plasma-dataengine-devicenotifications.desktop --- a/dataengines/devicenotifications/plasma-dataengine-devicenotifications.desktop +++ b/dataengines/devicenotifications/plasma-dataengine-devicenotifications.desktop @@ -6,7 +6,7 @@ Name[bs]=Obavještenja o uređajima Name[ca]=Notificacions dels dispositius Name[ca@valencia]=Notificacions dels dispositius -Name[cs]=Oznamování zařízení +Name[cs]=Upozornění zařízení Name[da]=Enhedsbekendtgørelser Name[de]=Geräte-Benachrichtigungen Name[el]=Ειδοποιήσεις συσκευών @@ -102,7 +102,7 @@ Comment[pt]=Notificações passivas de dispositivos para o utilizador. Comment[pt_BR]=Notificações passivas de dispositivos para o usuário. Comment[ro]=Notificări de dispozitiv pasive pentru utilizator. -Comment[ru]=Пассивные уведомления для пользователя об оборудовании. +Comment[ru]=Пассивные уведомления об оборудовании для пользователя. Comment[si]=පරිශීලකයන් සඳහා නිශ්ක්‍රීය මෙවලම් දැනුම්දීම්. Comment[sk]=Pasívne upozornenia zariadení pre užívateľa. Comment[sl]=Pasivna obvestila o napravah za uporabnika. diff --git a/dataengines/dict/plasma-dataengine-dict.desktop b/dataengines/dict/plasma-dataengine-dict.desktop --- a/dataengines/dict/plasma-dataengine-dict.desktop +++ b/dataengines/dict/plasma-dataengine-dict.desktop @@ -2,6 +2,7 @@ Name=Dictionary Name[ar]=قاموس Name[as]=অভিধান +Name[ast]=Diccionariu Name[be@latin]=Słoŭnik Name[bg]=Речник Name[bn]=অভিধান @@ -80,12 +81,13 @@ Comment=Look up word meanings Comment[af]=Slaan woordbetekenisse na Comment[ar]=ابحث عن معاني الكلمات +Comment[ast]=Gueta significaos de pallabres Comment[be@latin]=Pošuk značeńniaŭ słovaŭ Comment[bg]=Търсене значението на думи Comment[bn_IN]=শব্দের অর্থ অনুসন্ধান করুন Comment[bs]=Potražite značenja riječi Comment[ca]=Cerca els significats de paraules -Comment[ca@valencia]=Cerca els significats de paraules +Comment[ca@valencia]=Busca els significats de les paraules Comment[cs]=Vyhledávání významu slov Comment[da]=Slå ords betydning op Comment[de]=Nachschlagen von Wortbedeutungen @@ -115,7 +117,7 @@ Comment[km]=រក​មើល​អត្ថន័យ​របស់​ពាក្យ Comment[kn]=ಪದಗಳ ಅರ್ಥಗಳಿಗನ್ನು ಹುಡುಕು Comment[ko]=단어의 뜻 찾기 -Comment[lt]=Žodžių prasmės paieška +Comment[lt]=Žodžių reikšmių paieška Comment[lv]=Atrod vārdu nozīmes Comment[mk]=Побарајте за значења на зборовите Comment[ml]=വാക്കുകളുടെ അര്‍ത്ഥങ്ങള്‍ നിഘണ്ടുവില്‍ തെരയുക @@ -142,7 +144,6 @@ Comment[sv]=Slå upp ords betydelse Comment[ta]=Look up word meanings Comment[te]=పదము అర్ధాలను చూడుము -Comment[tg]=Выяснение значения слов Comment[th]=ค้นหาความหมายของคำ Comment[tr]=Sözcük anlamlarına bak Comment[ug]=سۆز مەنىسىنى ئىزدە diff --git a/dataengines/executable/plasma-dataengine-executable.desktop b/dataengines/executable/plasma-dataengine-executable.desktop --- a/dataengines/executable/plasma-dataengine-executable.desktop +++ b/dataengines/executable/plasma-dataengine-executable.desktop @@ -64,7 +64,6 @@ Name[sr@latin]=izvršavanje naredbi Name[sv]=Kör kommandon Name[ta]=இயக்க ஆணைகள் -Name[tg]=Иҷрои фармонҳо Name[th]=ประมวลผลคำสั่ง Name[tr]=Komut Çalıştır Name[ug]=بۇيرۇقلارنى ئىجرا قىل @@ -109,8 +108,8 @@ Comment[kn]=ಚಾಲನಾಯೋಗ್ಯವಾದ ದತ್ತ ಸಾಧನವನ್ನು ಚಲಾಯಿಸಿ Comment[ko]=명령 실행 데이터 엔진 Comment[ku]=Motora Dane yên Tên Xebitandin Bixebitîne -Comment[lt]=Leisti vykdomąjį duomenų variklį -Comment[lv]=Izpildfailu palaišanas datu dzinējs +Comment[lt]=Paleisti vykdomąjį duomenų variklį +Comment[lv]=Izpilddatņu palaišanas datu dzinējs Comment[ml]=പ്രവര്‍ത്തിക്കാന്‍ കഴിയുന്ന ഡേറ്റാ എഞ്ചിന്‍ പ്രവര്‍ത്തിപ്പിയ്ക്കുക Comment[mr]=कार्यान्वितजोगी Data Engine चालवा Comment[nb]=Datamotor for kjørbare filer/programmer @@ -133,7 +132,6 @@ Comment[sr@latin]=Datomotor pokretanja izvršnih Comment[sv]=Datagränssnitt för att köra program Comment[ta]=இயக்க வல்ல தேடு பொறி -Comment[tg]=Поставщик лент RSS Comment[th]=กลไกข้อมูลการประมวลผลคำสั่ง Comment[tr]=Çalıştırılabilir Veri Motorunu Başlat Comment[ug]=ئىجراچان سانلىق-مەلۇمات ماتورىنى ئىجرا قىل diff --git a/dataengines/favicons/faviconprovider.h b/dataengines/favicons/faviconprovider.h --- a/dataengines/favicons/faviconprovider.h +++ b/dataengines/favicons/faviconprovider.h @@ -25,7 +25,6 @@ #include class QImage; -class KJob; /** * This class provides a favicon for a given url @@ -83,7 +82,6 @@ class Private; Private* const d; - Q_PRIVATE_SLOT( d, void imageRequestFinished(KJob *job) ) }; #endif diff --git a/dataengines/favicons/faviconprovider.cpp b/dataengines/favicons/faviconprovider.cpp --- a/dataengines/favicons/faviconprovider.cpp +++ b/dataengines/favicons/faviconprovider.cpp @@ -38,22 +38,21 @@ { } - void imageRequestFinished( KJob *job ); + void imageRequestFinished(KIO::StoredTransferJob *job); FaviconProvider *q; QImage image; QString cachePath; }; -void FaviconProvider::Private::imageRequestFinished(KJob *job) +void FaviconProvider::Private::imageRequestFinished(KIO::StoredTransferJob *job) { if (job->error()) { emit q->error(q); return; } - KIO::StoredTransferJob *storedJob = qobject_cast(job); - image = QImage::fromData(storedJob->data()); + image = QImage::fromData(job->data()); if (!image.isNull()) { image.save(cachePath, "PNG"); } @@ -81,7 +80,9 @@ if (faviconUrl.isValid()) { KIO::StoredTransferJob *job = KIO::storedGet(faviconUrl, KIO::NoReload, KIO::HideProgressInfo); //job->setProperty("uid", id); - connect(job, SIGNAL(result(KJob*)), this, SLOT(imageRequestFinished(KJob*))); + connect(job, &KJob::result, this, [this, job]() { + d->imageRequestFinished(job); + }); } } } diff --git a/dataengines/favicons/plasma-dataengine-favicons.desktop b/dataengines/favicons/plasma-dataengine-favicons.desktop --- a/dataengines/favicons/plasma-dataengine-favicons.desktop +++ b/dataengines/favicons/plasma-dataengine-favicons.desktop @@ -38,7 +38,7 @@ Name[kn]=ಫೆವಿಕಾನ್‌ಗಳು Name[ko]=파비콘 Name[ku]=Nîşanên Malperan -Name[lt]=Srities ženkliukai +Name[lt]=Svetainių piktogramos Name[lv]=TīmekļaIkonas Name[mai]=फेविकान Name[mk]=Омилени икони @@ -64,7 +64,6 @@ Name[sr@latin]=favikone Name[sv]=Favoritikoner Name[ta]=சின்னங்கள் -Name[tg]=Нишонаҳои саҳифаи интернетӣ Name[th]=ไอคอนเว็บ Name[tr]=Site Simgeleri Name[ug]=تور بېكەت سىنبەلگە @@ -79,7 +78,7 @@ Comment[bg]=Ядро за изтегляне на уеб-икони от сайтове Comment[bs]=Datomotor za dobavljanje favikona veb sajtova Comment[ca]=Motor de dades per obtenir icones de web dels llocs web -Comment[ca@valencia]=Motor de dades per obtindre icones de web dels llocs web +Comment[ca@valencia]=Motor de dades per a obtindre icones de web dels llocs web Comment[cs]=Mechanismus pro získávání ikon webových stránek Comment[da]=Datamotor til at hente favicons fra hjemmesider Comment[de]=Daten-Treiber zum Holen von Webseitensymbolen von Webseiten @@ -108,7 +107,7 @@ Comment[km]=ម៉ាស៊ីន​ទិន្នន័យ​សម្រាប់​ទទួល​រូបតំណាង​សំណព្វ​របស់​តំបន់​បណ្ដាញ Comment[kn]=ಜಾಲ ತಾಣಗಳ ಫೆವಿಕಾನ್‌ಗಳನ್ನು ಪಡೆಯಲು ನೆರವಾಗುವ ದತ್ತ ಎಂಜಿನ್ Comment[ko]=웹 사이트의 파비콘을 가져오는 데이터 엔진 -Comment[lt]=Duomenų variklis srities ženkliukų atsisiuntimui iš interneto +Comment[lt]=Duomenų variklis, skirtas svetainių piktogramų gavimui Comment[lv]=Datu dzinējs tīmekļa vietņu ikonu ielādei Comment[mk]=Податочна машина за добивање омилени икони од веб-локации Comment[ml]=വെബ് സൈറ്റുകളുടെ ഇഷ്ടചിഹ്നങ്ങള്‍ ലഭിക്കാനുള്ള ഡേറ്റ എഞ്ചിന്‍ diff --git a/dataengines/filebrowser/plasma-dataengine-filebrowser.desktop b/dataengines/filebrowser/plasma-dataengine-filebrowser.desktop --- a/dataengines/filebrowser/plasma-dataengine-filebrowser.desktop +++ b/dataengines/filebrowser/plasma-dataengine-filebrowser.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Files and Directories Name[ar]=ملفات وأدلة +Name[ast]=Ficheros y direutorios Name[be@latin]=Fajły j katalohi Name[bg]=Файлове и папки Name[bn]=ফাইল এবং ডিরেক্টরি @@ -38,8 +39,8 @@ Name[km]=ឯកសារ​ និង​ថត Name[kn]=ಕಡತ ಹಾಗೂ ಕಡಕಕೋಶಗಳು Name[ko]=파일과 디렉터리 -Name[lt]=Failai ir aplankai -Name[lv]=Faili un Mapes +Name[lt]=Failai ir katalogai +Name[lv]=Datnes un mapes Name[mai]=फाइल आओर निर्देशिका Name[mk]=Датотеки и папки Name[ml]=ഫയല്‍, തട്ട് എന്നിവ @@ -64,7 +65,7 @@ Name[sr@latin]=fajlovi i fascikle Name[sv]=Filer och kataloger Name[ta]=கோப்புகளும் அடைவுகளும் -Name[tg]=Проводник +Name[tg]=Файлҳо ва ҷузвадонҳо Name[th]=ดูแฟ้มและไดเรกทอรี Name[tr]=Dosyalar ve Dizinler Name[ug]=ھۆججەت ۋە مۇندەرىجەلەر @@ -76,6 +77,7 @@ Name[zh_TW]=檔案與目錄 Comment=Information about files and directories. Comment[ar]=معلومات حول الملفات والأدلة. +Comment[ast]=Informacón tocante a ficheros y direutorios Comment[bg]=Данни за файлове и папки. Comment[bn]=ফাইল এবং ডিরেক্টরি সংক্রান্ত তথ্য। Comment[bs]=Podaci o datotekema i fasciklama. @@ -110,8 +112,8 @@ Comment[km]=ព័ត៌មាន​អំពី​ឯកសារ និង​ថត ។ Comment[kn]=ಕಡತಗಳು ಹಾಗೂ ಕಡತಕೋಶಗಳ ಬಗೆಗೆ ಮಾಹಿತಿ. Comment[ko]=파일과 디렉터리 정보입니다. -Comment[lt]=Informacija apie failus ir aplankus. -Comment[lv]=Informācija par failiem un mapēm. +Comment[lt]=Informacija apie failus ir katalogus. +Comment[lv]=Informācija par datnēm un mapēm. Comment[mk]=Информации за датотеки и папки. Comment[ml]=ഫയലുകളുടേയും തട്ടുകളുടേയും വിവരങ്ങള്‍. Comment[mr]=फाईल्स व संचयीका विषयी माहिती. @@ -133,7 +135,6 @@ Comment[sr@ijekavianlatin]=Podaci o fajlovima i fasciklama. Comment[sr@latin]=Podaci o fajlovima i fasciklama. Comment[sv]=Information om filer och kataloger. -Comment[tg]=Информация о папках и файлах для плазмоидов Comment[th]=ข้อมูลเกี่ยวกับแฟ้มและไดเรกทอรีต่าง ๆ Comment[tr]=Dosyalar ve dizinler hakkında bilgiler. Comment[ug]=ھۆججەت ۋە مۇندەرىجەلەر ھەققىدە ئۇچۇرلار diff --git a/dataengines/geolocation/CMakeLists.txt b/dataengines/geolocation/CMakeLists.txt --- a/dataengines/geolocation/CMakeLists.txt +++ b/dataengines/geolocation/CMakeLists.txt @@ -50,6 +50,7 @@ # ------------------------------------------------------------------------------------------------- set(plasma_geolocation_ip_SRCS location_ip.cpp) +ecm_qt_declare_logging_category(plasma_geolocation_ip_SRCS HEADER geolocdebug.h IDENTIFIER DATAENGINE_GEOLOCATION CATEGORY_NAME org.kde.plasma.dataengine.geolocation) add_library(plasma-geolocation-ip MODULE ${plasma_geolocation_ip_SRCS}) target_compile_definitions(plasma-geolocation-ip PRIVATE -DQT_NO_KEYWORDS) target_link_libraries(plasma-geolocation-ip plasma-geolocation-interface KF5::KIOCore KF5::NetworkManagerQt) @@ -61,12 +62,13 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) find_package(libgps) set_package_properties(libgps PROPERTIES DESCRIPTION "GPS support for geolocation" - URL "http://gpsd.berlios.de/" + URL "https://www.berlios.de/software/gpsd/" TYPE OPTIONAL ) if(LIBGPS_FOUND) include_directories(${LIBGPS_INCLUDES} ${LIBGPS_INCLUDE_DIR}) set(plasma_geolocation_gps_SRCS location_gps.cpp) + ecm_qt_declare_logging_category(plasma_geolocation_gps_SRCS HEADER geolocdebug.h IDENTIFIER DATAENGINE_GEOLOCATION CATEGORY_NAME org.kde.plasma.dataengine.geolocation) add_library(plasma-geolocation-gps MODULE ${plasma_geolocation_gps_SRCS}) target_link_libraries(plasma-geolocation-gps plasma-geolocation-interface ${LIBGPS_LIBRARIES}) install(FILES plasma-geolocation-gps.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) diff --git a/dataengines/geolocation/location_gps.cpp b/dataengines/geolocation/location_gps.cpp --- a/dataengines/geolocation/location_gps.cpp +++ b/dataengines/geolocation/location_gps.cpp @@ -16,7 +16,7 @@ */ #include "location_gps.h" -#include +#include "geolocdebug.h" Gpsd::Gpsd(gps_data_t* gpsdata) : m_gpsdata(gpsdata) @@ -59,7 +59,11 @@ if (gps_poll(m_gpsdata) != -1) { #endif //qDebug() << "poll ok"; +#if GPSD_API_MAJOR_VERSION >= 9 + if (m_gpsdata->online.tv_sec || m_gpsdata->online.tv_nsec) { +#else if (m_gpsdata->online) { +#endif //qDebug() << "online"; if (m_gpsdata->status != STATUS_NO_FIX) { //qDebug() << "fix"; @@ -90,12 +94,12 @@ gps_data_t* m_gpsdata = gps_open("localhost", DEFAULT_GPSD_PORT); if (m_gpsdata) { #endif - qDebug() << "gpsd found."; + qCDebug(DATAENGINE_GEOLOCATION)<< "gpsd found."; m_gpsd = new Gpsd(m_gpsdata); connect(m_gpsd, SIGNAL(dataReady(Plasma::DataEngine::Data)), this, SLOT(setData(Plasma::DataEngine::Data))); } else { - qDebug() << "gpsd not found"; + qCWarning(DATAENGINE_GEOLOCATION) << "gpsd not found"; } setIsAvailable(m_gpsd); diff --git a/dataengines/geolocation/location_ip.cpp b/dataengines/geolocation/location_ip.cpp --- a/dataengines/geolocation/location_ip.cpp +++ b/dataengines/geolocation/location_ip.cpp @@ -20,7 +20,8 @@ */ #include "location_ip.h" -#include +#include "geolocdebug.h" +#include #include #include #include @@ -40,7 +41,7 @@ { m_geoLocationResolved = true; if (job && job->error()) { - qDebug() << "error" << job->errorString(); + qCCritical(DATAENGINE_GEOLOCATION) << "error: " << job->errorString(); m_geoLocationPayload.clear(); checkUpdateData(); return; @@ -93,7 +94,7 @@ void readCountry(KJob *job) { m_countryResolved = true; if (job && job->error()) { - qDebug() << "error" << job->errorString(); + qCCritical(DATAENGINE_GEOLOCATION) << "error: " << job->errorString(); m_countryPayload.clear(); checkUpdateData(); return; @@ -179,7 +180,7 @@ KIO::HideProgressInfo); datajob->addMetaData(QStringLiteral("content-type"), QStringLiteral("application/json")); - qDebug() << "Fetching https://location.services.mozilla.com/v1/geolocate"; + qCDebug(DATAENGINE_GEOLOCATION) << "Fetching https://location.services.mozilla.com/v1/geolocate"; connect(datajob, &KIO::TransferJob::data, d, &Ip::Private::geoLocationData); connect(datajob, &KIO::TransferJob::result, d, &Ip::Private::readGeoLocation); diff --git a/dataengines/geolocation/plasma-dataengine-geolocation.desktop b/dataengines/geolocation/plasma-dataengine-geolocation.desktop --- a/dataengines/geolocation/plasma-dataengine-geolocation.desktop +++ b/dataengines/geolocation/plasma-dataengine-geolocation.desktop @@ -32,7 +32,7 @@ Name[km]=ទីតាំង​ភូមិសាស្ដ្រ Name[kn]=ಭೂಪ್ರದೇಶ Name[ko]=위치 -Name[lt]=Geolokacija +Name[lt]=Geografinės vietos nustatymas Name[lv]=Ģeolokācija Name[mk]=Геолоцирање Name[ml]=ഭൂസ്ഥാനങ്ങള്‍ @@ -55,7 +55,7 @@ Name[sr@ijekavianlatin]=geolokacija Name[sr@latin]=geolokacija Name[sv]=Geografisk lokalisering -Name[tg]=Ҷойгиршавӣ +Name[tg]=Ҷойгиршавии ҷуғрофӣ Name[th]=การระบุพิกัดตำแหน่ง Name[tr]=Geolocation Name[ug]=جۇغراپىيىلىك ئورۇن @@ -98,7 +98,7 @@ Comment[km]=ម៉ាស៊ីន​ទិន្នន័យ​ Geolocation Comment[kn]=ಭೂಪ್ರದೇಶ ದತ್ತ ಯಂತ್ರ Comment[ko]=위치 데이터 엔진 -Comment[lt]=Geolokacijos duomenų variklis +Comment[lt]=Geografinės vietos nustatymo duomenų variklis Comment[lv]=Ģeolokācijas datu dzinējs Comment[mai]=भूअवस्थिति डाटा इंजिन Comment[mk]=Машина за податоци за геолоцирање @@ -122,7 +122,7 @@ Comment[sr@ijekavianlatin]=Datomotor geolokacije Comment[sr@latin]=Datomotor geolokacije Comment[sv]=Datagränssnitt för geografisk lokalisering -Comment[tg]=Барномаҳои луғат +Comment[tg]=Низоми иттилоотии ҷойгиршавӣ ҷуғрофӣ Comment[th]=กลไกข้อมูลการระบุพิกัดตำแหน่ง Comment[tr]=Geolocation Veri Motoru Comment[ug]=جۇغراپىيىلىك ئورۇن سانلىق-مەلۇمات ماتورى @@ -142,7 +142,7 @@ X-KDE-PluginInfo-Email=damu@iki.fi X-KDE-PluginInfo-Name=geolocation X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=http://plasma@kde.org +X-KDE-PluginInfo-Website=https://kde.org/plasma-desktop X-KDE-PluginInfo-Category= X-KDE-PluginInfo-Depends= diff --git a/dataengines/geolocation/plasma-geolocation-gps.desktop b/dataengines/geolocation/plasma-geolocation-gps.desktop --- a/dataengines/geolocation/plasma-geolocation-gps.desktop +++ b/dataengines/geolocation/plasma-geolocation-gps.desktop @@ -31,7 +31,7 @@ Name[km]=ទីតាំង​ភូមិសាស្ត្រ GPS Name[kn]=ಭೂಪ್ರದೇಶದ GPS Name[ko]=GPS 위치 -Name[lt]=Geolokacinis GPS +Name[lt]=Geografinės vietos nustatymas pagal GPS Name[lv]=Ģeolokācija GPS Name[mai]=भूअवस्थिति जीपीएस Name[mk]=ГПС за геолоцирање @@ -55,7 +55,7 @@ Name[sr@ijekavianlatin]=geolokacija GPS Name[sr@latin]=geolokacija GPS Name[sv]=Lokalisera geografiskt med GPS -Name[tg]=Ҷойгиршавии GPS +Name[tg]=Ҷойгиршавии ҷуғрофии GPS Name[th]=การระบุพิกัดตำแหน่งด้วย GPS Name[tr]=Geolocation GPS Name[ug]=جۇغراپىيىلىك ئورۇن GPS @@ -97,7 +97,7 @@ Comment[km]=ទីតាំង​ភូមិសាស្ត្រ​ពី​អាយដ្ឋាន GPS ។ Comment[kn]=GPS ವಿಳಾಸದಿಂದ ಭೂಪ್ರದೇಶ. Comment[ko]=GPS 위치에 따른 주소입니다. -Comment[lt]=Geolokacija pagal GPS adresą. +Comment[lt]=Geografinės vietos nustatymas pagal GPS adresą. Comment[lv]=Ģeolokācija no GPS adreses. Comment[mai]=GPS पता सँ भूअवस्थिति. Comment[mk]=Геолокација преку ГПС-адреса. @@ -121,7 +121,6 @@ Comment[sr@ijekavianlatin]=Geolociranje kroz GPS adresu. Comment[sr@latin]=Geolociranje kroz GPS adresu. Comment[sv]=Geografisk lokalisering från GPS-adress. -Comment[tg]=Ҷойгиршавии ҷуғрофӣ аз суроғаи GPS Comment[th]=การระบุพิกัดตำแหน่งจากพิกัด GPS Comment[tr]=GPS adresinden coğrafi konum. Comment[ug]=GPS ئادرېسنىڭ جۇغراپىيىلىك ئورنى diff --git a/dataengines/geolocation/plasma-geolocation-ip.desktop b/dataengines/geolocation/plasma-geolocation-ip.desktop --- a/dataengines/geolocation/plasma-geolocation-ip.desktop +++ b/dataengines/geolocation/plasma-geolocation-ip.desktop @@ -31,7 +31,7 @@ Name[km]=IP ទីតាំង​ភូមិសាស្ត្រ Name[kn]=ಭೂಪ್ರದೇಶದ IP Name[ko]=IP 위치 -Name[lt]=Geolokacija pagal IP +Name[lt]=Geografinės vietos nustatymas pagal IP Name[lv]=Ģeolokācija IP Name[mai]=भूअवस्थिति IP Name[mk]=ИП за геолоцирање @@ -55,7 +55,7 @@ Name[sr@ijekavianlatin]=geolokacija IP Name[sr@latin]=geolokacija IP Name[sv]=Lokalisera geografiskt med IP-adress -Name[tg]=Ҷойгиршавии IP +Name[tg]=Ҷойгиршавии ҷуғрофии IP Name[th]=การระบุพิกัดตำแหน่งจากไอพี Name[tr]=Geolocation IP Name[ug]=جۇغراپىيىلىك ئورۇن IP @@ -97,7 +97,7 @@ Comment[km]=ទីតាំង​ភូមិសាស្ត្រ​ពី​​អាសយដ្ឋាន IP ។ Comment[kn]=IP ವಿಳಾಸದಿಂದ ಭೂಪ್ರದೇಶ. Comment[ko]=IP에서 얻어낸 주소입니다. -Comment[lt]=Geolokacija pagal IP adresą. +Comment[lt]=Geografinės vietos nustatymas pagal IP adresą. Comment[lv]=Ģeolokācija no IP adreses. Comment[mai]=IP पता से भूअवस्थिति. Comment[mk]=Геолокација преку ИП-адреса. @@ -121,7 +121,6 @@ Comment[sr@ijekavianlatin]=Geolociranje kroz IP adresu. Comment[sr@latin]=Geolociranje kroz IP adresu. Comment[sv]=Geografisk lokalisering från IP-adress. -Comment[tg]=Ҷойгиршавии сурағаи интернетӣ. Comment[th]=การระบุพิกัดตำแหน่งจากที่อยู่ไอพี Comment[tr]=IP adresinden coğrafi konum. Comment[ug]=IP ئادرېسنىڭ جۇغراپىيىلىك ئورنى. diff --git a/dataengines/geolocation/plasma-geolocationprovider.desktop b/dataengines/geolocation/plasma-geolocationprovider.desktop --- a/dataengines/geolocation/plasma-geolocationprovider.desktop +++ b/dataengines/geolocation/plasma-geolocationprovider.desktop @@ -6,7 +6,7 @@ Comment[bg]=Доставчик на геолокация Plasma Comment[bs]=Plazma dobavljač geolokacije Comment[ca]=Proveïdor de geolocalització del Plasma -Comment[ca@valencia]=Proveïdor de geolocalització del Plasma +Comment[ca@valencia]=Proveïdor de geolocalització de Plasma Comment[cs]=Poskytovatel geolokace Plasma Comment[csb]=Plazma geògrafny lokalizacëji Comment[da]=Plasma geo-lokalisering-udbyder @@ -34,7 +34,7 @@ Comment[km]=ក្រុមហ៊ុន​ផ្ដល់​ទីតាំង​ភូមិសាស្ត្រ​ប្លាស្មា Comment[kn]=ಪ್ಲಾಸ್ಮಾ ಭೂಪ್ರದೇಶ ಒದಗಿಸುವ ಸಾಧನ Comment[ko]=Plasma 위치 공급자 -Comment[lt]=Plasma geolokacijos paslaugos teikiklis +Comment[lt]=Plasma geografinės vietos nustatymo tiekėjas Comment[lv]=Plasma ģeolokācijas piegādātājs Comment[mai]=प्लाज्मा भूअवस्थिति प्रदाता Comment[ml]=പ്ലാസ്മയുടെ ഭൂസ്ഥാന ധാതാവ് @@ -57,7 +57,6 @@ Comment[sr@ijekavianlatin]=Plasma dobavljač geolokacije Comment[sr@latin]=Plasma dobavljač geolokacije Comment[sv]=Plasma geografisk lokalisering -Comment[tg]=Барномаҳои луғат Comment[th]=ผู้ให้บริการพิกัดตำแหน่งของพลาสมา Comment[tr]=Plasma Geolocation Sağlayıcı Comment[ug]=پلازما جۇغراپىيىلىك ئورۇن تەمىنلىگۈچى diff --git a/dataengines/hotplug/hotplugengine.h b/dataengines/hotplug/hotplugengine.h --- a/dataengines/hotplug/hotplugengine.h +++ b/dataengines/hotplug/hotplugengine.h @@ -48,7 +48,7 @@ void onDeviceRemoved(const QString &udi); private: - void onDeviceAdded(Solid::Device &dev, bool added = true); + void handleDeviceAdded(Solid::Device &dev, bool added = true); void findPredicates(); QStringList predicatesForDevice(Solid::Device &device) const; QVariantList actionsForPredicates(const QStringList &predicates) const; diff --git a/dataengines/hotplug/hotplugengine.cpp b/dataengines/hotplug/hotplugengine.cpp --- a/dataengines/hotplug/hotplugengine.cpp +++ b/dataengines/hotplug/hotplugengine.cpp @@ -78,8 +78,8 @@ m_startList.insert(dev.udi(), dev); } - connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), - this, SLOT(onDeviceAdded(QString))); + connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, + this, &HotplugEngine::onDeviceAdded); connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, this, &HotplugEngine::onDeviceRemoved); @@ -98,7 +98,7 @@ if (!m_startList.isEmpty()) { QHash::iterator it = m_startList.begin(); //Solid::Device dev = const_cast(m_startList.takeFirst()); - onDeviceAdded(it.value(), false); + handleDeviceAdded(it.value(), false); m_startList.erase(it); } @@ -153,7 +153,7 @@ data.insert(QStringLiteral("actions"), actionsForPredicates(predicates)); setData(udi, data); } else { - onDeviceAdded(device, false); + handleDeviceAdded(device, false); } } else if (!m_encryptedPredicate.matches(device) && sources().contains(udi)) { removeSource(udi); @@ -201,10 +201,10 @@ void HotplugEngine::onDeviceAdded(const QString &udi) { Solid::Device device(udi); - onDeviceAdded(device); + handleDeviceAdded(device); } -void HotplugEngine::onDeviceAdded(Solid::Device &device, bool added) +void HotplugEngine::handleDeviceAdded(Solid::Device &device, bool added) { //qDebug() << "adding" << device.udi(); #ifdef HOTPLUGENGINE_TIMING diff --git a/dataengines/hotplug/plasma-dataengine-hotplug.desktop b/dataengines/hotplug/plasma-dataengine-hotplug.desktop --- a/dataengines/hotplug/plasma-dataengine-hotplug.desktop +++ b/dataengines/hotplug/plasma-dataengine-hotplug.desktop @@ -35,7 +35,7 @@ Name[km]=ព្រឹត្តិការណ៍​ដោត​ដើរ Name[kn]=Hotplug ಘಟನೆಗಳು (ಈವೆಂಟ್) Name[ko]=핫플러그 이벤트 -Name[lt]=Hotplug įvykiai +Name[lt]=Greitojo prijungimo įvykiai Name[lv]=Hotplug notikumi Name[ml]=ഹോട്ട്പ്ലഗ് സംഭവങ്ങള്‍ Name[mr]=हॉटप्लग घटना @@ -58,7 +58,6 @@ Name[sr@latin]=događaji vrućeg uključivanja Name[sv]=Inkopplingshändelser Name[ta]=Hotplug Events -Name[tg]=Дастгоҳҳои пайвастшаванда Name[th]=เหตุการณ์อุปกรณ์ Hot Plug Name[tr]=Tak Kullan Olayları Name[ug]=قىزىق قىستۇرۇش ھادىسىسى @@ -96,7 +95,7 @@ Comment[km]=តាមដាន​ឧបករណ៍​ដែល​អាច​ដោត​ដើរ​ដូច​ពួកវា​បង្ហាញ និង​មិន​បង្ហាញ ។ Comment[kn]=ಹಾಟ್‌-ಪ್ಲಗ್‌ ಮಾಡಬಹುದಾದಂತಹ ಸಾಧನಗಳು ಕಾಣಿಸಿಕೊಂಡಾಗ ಹಾಗು ಅಡಗಿಸಿದಾಗ ಅವುಗಳ ಜಾಡನ್ನು ಇರಿಸುತ್ತದೆ. Comment[ko]=장치가 연결되고 해제될 때 기록합니다. -Comment[lt]=Seka įrenginių prijungimą ir atjungimą. +Comment[lt]=Seka greitai prijungiamų įrenginių atsiradimą ir išnykimą. Comment[lv]=Seko līdzi noņemamo ierīču pieslēgšanai un atvienošanai. Comment[ml]=കമ്പ്യൂട്ടര്‍ പ്രവര്‍ത്തിച്ചു് കൊണ്ടിരിക്കുമ്പോള്‍ കുത്താവുന്ന ഉപകരണങ്ങള്‍ പ്രത്യക്ഷപ്പെടുന്നതും അപ്രത്യക്ഷമാകുന്നതും നിരീക്ഷിക്കുന്നു. Comment[mr]=हॉटप्लग करता येणाऱ्या साधनांवर लक्ष ठेवतो. diff --git a/dataengines/keystate/plasma-dataengine-keystate.desktop b/dataengines/keystate/plasma-dataengine-keystate.desktop --- a/dataengines/keystate/plasma-dataengine-keystate.desktop +++ b/dataengines/keystate/plasma-dataengine-keystate.desktop @@ -1,11 +1,12 @@ [Desktop Entry] Name=Keyboard and Mouse State Name[ar]=حالة الفارة ولوحة المفاتيح +Name[ast]=Estáu del tecláu y mur Name[bg]=Състояние на клавиатура и мишка Name[bn]=কীবোর্ড ও মাউস অবস্থা Name[bs]=Stanje tastature i miša Name[ca]=Estat del teclat i ratolí -Name[ca@valencia]=Estat del teclat i ratolí +Name[ca@valencia]=Estat del teclat i del ratolí Name[cs]=Stav klávesnice a myši Name[csb]=Stón klawiaturë ë mëszë Name[da]=Tilstand for tastatur og mus @@ -35,7 +36,7 @@ Name[km]=ស្ថានភាព​ក្ដារចុច និង​កណ្ដុល Name[kn]=ಕೀಲಿಮಣೆ ಮತ್ತು ಮೌಸ್‌ನ ಸ್ಥಿತಿ Name[ko]=키보드와 마우스 상태 -Name[lt]=Klaviatūros ir pelės būklė +Name[lt]=Klaviatūros ir pelės būsena Name[lv]=Tastatūra un peles stāvoklis Name[mk]=Состојба на тастатура и глушец Name[ml]=കീബോര്‍ഡിന്റേയും മൌസിന്റേയും അവസ്ഥ @@ -70,6 +71,7 @@ Name[zh_TW]=鍵盤與滑鼠狀態 Comment=Keyboard modifier and mouse buttons states Comment[ar]=حالات مُعدِّل لوحة المفاتيح وأزرار الفأرة +Comment[ast]=Estaos de los botones del mur y modificadores del tecláu Comment[bg]=Състояние на клавиатурни модификатори и бутони на мишката Comment[bs]=Stanja modifikatora na tastaturi i dugmadi miša Comment[ca]=Modificador de l'estat del teclat i dels botons del ratolí @@ -99,7 +101,7 @@ Comment[km]=កម្មវិធី​កែប្រែ​ក្ដារចុច និង​ស្ថានភាព​ប៊ូតុង​កណ្ដុរ Comment[kn]=ಕೀಲಿಮಣೆ ಮಾರ್ಪಡಕ ಹಾಗು ಮೌಸ್ ಗುಂಡಿಗಳ ಸ್ಥಿತಿಗಳು Comment[ko]=키보드 수정자 키 및 마우스 단추 상태 -Comment[lt]=Klaviatūros būsenos ir pelės mygtukų būsenos stebėklis +Comment[lt]=Klaviatūros modifikatorių ir pelės mygtukų būsenos Comment[lv]=Tastatūras modifikatoru un peles pogu stāvoklis Comment[ml]=കീബോര്‍ഡ് മോഡിഫയറിന്റേയും മൌസ് ബട്ടണിന്റെയും സ്ഥിതി Comment[mr]=कळफलक परिवर्तक व माऊस बटन स्थिती diff --git a/dataengines/mouse/plasma-dataengine-mouse.desktop b/dataengines/mouse/plasma-dataengine-mouse.desktop --- a/dataengines/mouse/plasma-dataengine-mouse.desktop +++ b/dataengines/mouse/plasma-dataengine-mouse.desktop @@ -1,11 +1,12 @@ [Desktop Entry] Name=Pointer Position Name[ar]=موقع المؤشّر +Name[ast]=Posición del punteru Name[be@latin]=Miesca kursora Name[bg]=Разположение на курсора Name[bs]=Položaj pokazivača Name[ca]=Posició de l'apuntador -Name[ca@valencia]=Posició de l'apuntador +Name[ca@valencia]=Posició del punter Name[cs]=Pozice ukazatele Name[da]=Markørposition Name[de]=Mausposition @@ -27,7 +28,7 @@ Name[hr]=Pozicija pokazivača miša Name[hu]=Egérpozíció Name[ia]=Position de punctator -Name[id]=Posisi Penunjuk +Name[id]=Posisi Pointer Name[is]=Staðsetning bendils Name[it]=Posizione del puntatore Name[ja]=マウスポインタの位置 @@ -61,7 +62,6 @@ Name[sv]=Pekarposition Name[ta]=Pointer Position Name[te]=సూచకి స్థానము -Name[tg]=Положение указателя мыши Name[th]=ตำแหน่งของตัวชี้ Name[tr]=İşaretçi Konumu Name[ug]=نۇربەلگە ئورنى @@ -72,6 +72,7 @@ Name[zh_TW]=指標位置 Comment=Mouse position and cursor Comment[ar]=موقع ومؤشّر الفأرة +Comment[ast]=Cursor y posición del mur Comment[bg]=Позиция на мишката и курсора Comment[bs]=Položaj miša i pokazivača Comment[ca]=Posició del ratolí i del cursor @@ -103,7 +104,7 @@ Comment[km]=ទីតាំង និង​ទស្សន៍ទ្រនិច្ច​កណ្ដុរ Comment[kn]=ಮೌಸ್‌ನ ಸ್ಥಾನ ಹಾಗು ತೆರೆಸೂಚಕ Comment[ko]=마우스 위치와 커서 -Comment[lt]=Pelės pozicija ir kursorius +Comment[lt]=Pelės pozicija ir žymeklis Comment[lv]=Peles pozīcija un kursors Comment[mai]=माउसक स्थिति आओर कर्सर Comment[mk]=Позиција и покажувач на глушецот @@ -127,7 +128,6 @@ Comment[sr@ijekavianlatin]=Položaj miša i pokazivač Comment[sr@latin]=Položaj miša i pokazivač Comment[sv]=Musposition och pekare -Comment[tg]=Ҷойгиршавии муш ва курсор Comment[th]=ตำแหน่งตัวชี้และเคอร์เซอร์ของเมาส์ Comment[tr]=Fare konumu ve işaretçisi Comment[ug]=چاشقىنەك ئورنى ۋە نۇربەلگە diff --git a/dataengines/mpris2/mpris2engine.cpp b/dataengines/mpris2/mpris2engine.cpp --- a/dataengines/mpris2/mpris2engine.cpp +++ b/dataengines/mpris2/mpris2engine.cpp @@ -120,15 +120,26 @@ void Mpris2Engine::initialFetchFinished(PlayerContainer* container) { qCDebug(MPRIS2) << "Props fetch for" << container->objectName() << "finished; adding"; - addSource(container); - if (m_multiplexer) { - m_multiplexer.data()->addPlayer(container); - } + // don't let future refreshes trigger this disconnect(container, &PlayerContainer::initialFetchFinished, this, &Mpris2Engine::initialFetchFinished); disconnect(container, &PlayerContainer::initialFetchFailed, this, &Mpris2Engine::initialFetchFailed); + + // Check if the player follows the specification dutifully. + const auto data = container->data(); + if (data.value(QStringLiteral("Identity")).toString().isEmpty() + || !data.value(QStringLiteral("SupportedUriSchemes")).isValid() + || !data.value(QStringLiteral("SupportedMimeTypes")).isValid()) { + qCDebug(MPRIS2) << "MPRIS2 service" << container->objectName() << "isn't standard-compliant, ignoring"; + return; + } + + addSource(container); + if (m_multiplexer) { + m_multiplexer.data()->addPlayer(container); + } } void Mpris2Engine::initialFetchFailed(PlayerContainer* container) diff --git a/dataengines/mpris2/plasma-dataengine-mpris2.desktop b/dataengines/mpris2/plasma-dataengine-mpris2.desktop --- a/dataengines/mpris2/plasma-dataengine-mpris2.desktop +++ b/dataengines/mpris2/plasma-dataengine-mpris2.desktop @@ -1,5 +1,6 @@ [Desktop Entry] Name=MPRIS2 +Name[ast]=MPRIS2 Name[bs]=MPRIS2 Name[ca]=MPRIS2 Name[ca@valencia]=MPRIS2 @@ -43,6 +44,7 @@ Name[sr@ijekavianlatin]=MPRIS 2 Name[sr@latin]=MPRIS 2 Name[sv]=MPRIS2 +Name[tg]=MPRIS2 Name[tr]=MPRIS2 Name[uk]=MPRIS2 Name[x-test]=xxMPRIS2xx @@ -66,14 +68,14 @@ Comment[he]=מספק מידע ושליטה על מדיה דרך MPRIS2 Comment[hu]=Információkat szolgáltat és vezérli a médialejátszókat az MPRIS2-n keresztül Comment[ia]=Il provide information ex e control super media players via MPRIS2 -Comment[id]=Menyediakan informasi dari dan kendali player media via MPRIS2 +Comment[id]=Menyediakan informasi dari dan kendali pemutar media via MPRIS2 Comment[is]=Stýrir og gefur upplýsingar um margmiðlunarspilara með MPRIS2 Comment[it]=Fornisce le informazioni dai lettori multimediali e li controlla con MPRIS2 Comment[ja]=MPRIS2 を通して、メディアプレーヤーからの情報を提供し、メディアプレーヤーを制御します Comment[kk]=MPRIS2 арқылы медиаплейер мәліметін алып оны басқару мүмкіндігі Comment[km]=ផ្ដល់​ព័ត៌មាន​ពី និង​ត្រួតពិនិត្យ​លើ​កម្មវិធី​ចាក់​មេឌៀ​តាមរយៈ MPRIS2 Comment[ko]=MPRIS2를 통하여 미디어 재생기를 제어하고 정보 가져오기 -Comment[lt]=Pateikia informaciją iš MPRIS2 ir per jį valdo medijos grotuvus +Comment[lt]=Suteikia informaciją iš MPRIS2 ir per jį valdo medijos leistuves Comment[mr]=MPRIS2 द्वारे मीडिया प्लेयर्स वर नियंत्रण व माहिती पुरवितो Comment[nb]=Gir informasjon fra og kontroll over mediaspillere via MPRIS2-grensesnittet Comment[nds]=Stellt Informatschonen ut un Kuntrull över Medienafspelers över MPRIS2 praat diff --git a/dataengines/mpris2/playercontainer.cpp b/dataengines/mpris2/playercontainer.cpp --- a/dataengines/mpris2/playercontainer.cpp +++ b/dataengines/mpris2/playercontainer.cpp @@ -248,18 +248,18 @@ } else if (propName == QLatin1String("Rate") && data().value(QStringLiteral("PlaybackStatus")).toString() == QLatin1String("Playing")) { - if (data().contains(QStringLiteral("Position"))) + if (data().contains(QLatin1String("Position"))) recalculatePosition(); m_currentRate = value.toDouble(); } else if (propName == QLatin1String("PlaybackStatus")) { - if (data().contains(QStringLiteral("Position")) && data().contains(QStringLiteral("PlaybackStatus"))) { + if (data().contains(QLatin1String("Position")) && data().contains(QLatin1String("PlaybackStatus"))) { updatePosition(); } // update the effective rate - if (data().contains(QStringLiteral("Rate"))) { + if (data().contains(QLatin1String("Rate"))) { if (value.toString() == QLatin1String("Playing")) m_currentRate = data().value(QStringLiteral("Rate")).toDouble(); else diff --git a/dataengines/notifications/plasma-dataengine-notifications.desktop b/dataengines/notifications/plasma-dataengine-notifications.desktop --- a/dataengines/notifications/plasma-dataengine-notifications.desktop +++ b/dataengines/notifications/plasma-dataengine-notifications.desktop @@ -8,7 +8,7 @@ Name[bs]=Obavještenja programa Name[ca]=Notificacions de les aplicacions Name[ca@valencia]=Notificacions de les aplicacions -Name[cs]=Oznamování aplikací +Name[cs]=Upozornění aplikací Name[csb]=Dôwanié wiédzë ò aplikacëjach Name[da]=Programbekendtgørelser Name[de]=Anwendungs-Benachrichtigungen @@ -22,7 +22,7 @@ Name[fr]=Notifications des applications Name[fy]=Applikaasje ntifikaasjes Name[ga]=Fógairtí Feidhmchláir -Name[gl]=Notificacións dos aplicativos +Name[gl]=Notificacións das aplicacións Name[gu]=કાર્યક્રમ નોંધણીઓ Name[he]=הודעות יישומים Name[hi]=अनुप्रयोग सूचनाएँ @@ -64,7 +64,7 @@ Name[sr@latin]=obaveštenja programa Name[sv]=Programunderrättelser Name[ta]=அமைப்பு குறிப்பகள் -Name[tg]=Огоҳномаҳои система +Name[tg]=Огоҳиҳои барнома Name[th]=การแจ้งให้ทราบของโปรแกรม Name[tr]=Uygulama Bildirimleri Name[ug]=پروگرامما ئۇقتۇرۇشى @@ -75,6 +75,7 @@ Name[zh_CN]=应用程序通知 Name[zh_TW]=應用程式通知 Comment=Passive visual notifications for the user. +Comment[ast]=Avisos visuales pasivos pal usuariu. Comment[bg]=Пасивни визуални съобщения за потребителя. Comment[bs]=Pasivna vizuelna obavještenja za korisnika. Comment[ca]=Notificacions visuals passives per a l'usuari. @@ -103,7 +104,7 @@ Comment[km]=ការ​ជូនដំណឹង​ដែល​មើល​សម្រាប់​អ្នកប្រើ ។ Comment[kn]=ಬಳಕೆದಾರನ ನಿಷ್ಕ್ರಿಯವಾಗಿರುವ ದೃಶ್ಯ ಸೂಚನೆಗಳು. Comment[ko]=사용자에게 보이는 수동적인 알림입니다. -Comment[lt]=Pasyvūs vizualūs pranešimai naudotojui. +Comment[lt]=Pasyvūs vaizdiniai pranešimai naudotojui. Comment[lv]=Pasīvi vizuālie paziņojumi lietotājam. Comment[mk]=Пасивни визуелни известувања за корисникот. Comment[ml]=ഉപയോക്താവിനുള്ള നടപടി വേണ്ടാത്ത ദൃശ്യ സൂചനകള്‍ diff --git a/dataengines/packagekit/packagekitengine.cpp b/dataengines/packagekit/packagekitengine.cpp --- a/dataengines/packagekit/packagekitengine.cpp +++ b/dataengines/packagekit/packagekitengine.cpp @@ -42,7 +42,7 @@ if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() == 1) { QStringList list = reply.arguments().first().toStringList(); - if (list.contains(QStringLiteral("org.freedesktop.PackageKit"))) { + if (list.contains(QLatin1String("org.freedesktop.PackageKit"))) { m_pk_available = true; } } diff --git a/dataengines/packagekit/plasma-dataengine-packagekit.desktop b/dataengines/packagekit/plasma-dataengine-packagekit.desktop --- a/dataengines/packagekit/plasma-dataengine-packagekit.desktop +++ b/dataengines/packagekit/plasma-dataengine-packagekit.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=PackageKit Name[ar]=عُدّة الحزم +Name[ast]=PackageKit Name[bs]=Paket opreme Name[ca]=PackageKit Name[ca@valencia]=PackageKit @@ -49,9 +50,10 @@ Name[zh_TW]=PackageKit Comment=PackageKit Data Engine Comment[ar]=محرّك بيانات عُدّة الحزم +Comment[ast]=Motor de datos de PackageKit Comment[bs]=Pogon podataka za package kit Comment[ca]=Motor de dades del PackageKit -Comment[ca@valencia]=Motor de dades del PackageKit +Comment[ca@valencia]=Motor de dades de PackageKit Comment[cs]=Datový nástroj PackageKit Comment[da]=PackageKit datamotor Comment[de]=Daten-Treiber PackageKit diff --git a/dataengines/places/plasma-dataengine-places.desktop b/dataengines/places/plasma-dataengine-places.desktop --- a/dataengines/places/plasma-dataengine-places.desktop +++ b/dataengines/places/plasma-dataengine-places.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Places Name[ar]=أماكن +Name[ast]=Llugares Name[be@latin]=Miescy Name[bg]=Места Name[bn]=স্থান @@ -105,8 +106,8 @@ Comment[km]=កន្លែង ដូច​ដែល​បានឃើញ​នៅ​ក្នុង​កម្មវិធី​គ្រប់គ្រង​ឯកសារ និង​ប្រអប់​ឯកសារ ។ Comment[kn]=ಕಡತ ವ್ಯವಸ್ಥಾಪಕದಲ್ಲಿ ಹಾಗು ಕಡತ ಸಂವಾದಗಳಲ್ಲಿ ಕಾಣಿಸುವ ಸ್ಥಳಗಳು. Comment[ko]=파일 관리자와 파일 대화 상자에 나타나는 위치입니다. -Comment[lt]=Vietos, kaip jos matomos failų tvarkyklėje ir failų dialoguose. -Comment[lv]=Vietas, kas redzamas failu pārvaldniekā un failu atvēršanas logos. +Comment[lt]=Vietos, kaip jos yra matomos failų tvarkytuvėje ir failų dialoguose. +Comment[lv]=Vietas, kas redzamas datņu pārvaldniekā un datņu atvēršanas logos. Comment[mk]=Места, како што се гледаат во менаџерот на датотеки и во дијалозите за датотеки Comment[ml]=ഫയല്‍ കാര്യസ്ഥനിലും ഫയല്‍ സംഭാഷണങ്ങളിലും കാണുന്നപോലെയുള്ള ഇടങ്ങള്‍. Comment[mr]=फाईल व्यवस्थापक व फाईल संवादामध्ये दिसणाऱ्या जागा. diff --git a/dataengines/powermanagement/plasma-dataengine-powermanagement.desktop b/dataengines/powermanagement/plasma-dataengine-powermanagement.desktop --- a/dataengines/powermanagement/plasma-dataengine-powermanagement.desktop +++ b/dataengines/powermanagement/plasma-dataengine-powermanagement.desktop @@ -2,6 +2,7 @@ # ctxt: plasma data engine Name=Power Management Name[ar]=إدارة الطاقة +Name[ast]=Xestión d'enerxía Name[bg]=Управление на захранването Name[bn]=শক্তি ব্যবস্থাপনা Name[bs]=Upravljanje napajanjem @@ -98,7 +99,7 @@ Comment[km]=ព័ត៌មាន ថ្ម AC ដេក និង​ PowerDevil ។ Comment[kn]=ವಿದ್ಯುತ್ಕೋಶ, AC, ಜಡ ಹಾಗು PowerDevil ಮಾಹಿತಿ. Comment[ko]=배터리; AC; 절전 모드 및 PowerDevil 정보입니다. -Comment[lt]=Akumuliatoriaus, AC, miego ir PowerDevil informacija. +Comment[lt]=Akumuliatoriaus, AC, pristabdymo ir PowerDevil informacija. Comment[lv]=Baterija, barošana, gulēšanas un PowerDevil informācija. Comment[ml]=ബാറ്ററി, എസി, നിദ്ര പിന്നെ പവര്‍ഡെവിള്‍ എന്നീ വിവരങ്ങള്‍. Comment[mr]=बॅटरी, AC, व पॉवरडेव्हिल माहिती. @@ -111,16 +112,15 @@ Comment[pt]=Informação sobre a bateria, corrente eléctrica e o PowerDevil. Comment[pt_BR]=Bateria, adaptador de energia, dormir e informação do PowerDevil. Comment[ro]=Informații despre acumulator, priză, adormire și PowerDevil. -Comment[ru]=Информация о батарее, электрической сети и PowerDevil. +Comment[ru]=Информация о батареях, электрической сети и PowerDevil. Comment[si]=බැටරි, AC, නිද්‍රා හා PowerDevil තොරතුරු Comment[sk]=Informácie o batérii, AC a uspaní. Comment[sl]=Podatki o baterijah, omrežnem napajanju, pripravljenosti in PowerDevilu. Comment[sr]=Подаци из Струјног ђавола о батерији, АЦ‑у, и спавању. Comment[sr@ijekavian]=Подаци из Струјног ђавола о батерији, АЦ‑у, и спавању. Comment[sr@ijekavianlatin]=Podaci iz Strujnog đavola o bateriji, AC‑u, i spavanju. Comment[sr@latin]=Podaci iz Strujnog đavola o bateriji, AC‑u, i spavanju. Comment[sv]=Information om batteri, nätspänning, viloläge och Powerdevil. -Comment[tg]=Батарея, AC, захираи барқ ва иттилооти PowerDevil. Comment[th]=แบตเตอรี่, การเสียบปลั๊กไฟ, การพักเครื่อง และข้อมูลของ PowerDevil Comment[tr]=Pil, Adaptör, uyku kipi ve PowerDevil bilgileri. Comment[ug]=توكدان، ئۆزگىرىشچان توك مەنبەسى، سىستېما ئۇخلات ۋە PowerDevil ئۇچۇرىنى تەمىنلەيدۇ. diff --git a/dataengines/powermanagement/powermanagementengine.cpp b/dataengines/powermanagement/powermanagementengine.cpp --- a/dataengines/powermanagement/powermanagementengine.cpp +++ b/dataengines/powermanagement/powermanagementengine.cpp @@ -305,7 +305,7 @@ watcher->deleteLater(); }); - } else if (name == QLatin1Literal("Inhibitions")) { + } else if (name == QLatin1String("Inhibitions")) { QDBusMessage inhibitionsMsg = QDBusMessage::createMethodCall(SOLID_POWERMANAGEMENT_SERVICE, QStringLiteral("/org/kde/Solid/PowerManagement/PolicyAgent"), QStringLiteral("org.kde.Solid.PowerManagement.PolicyAgent"), diff --git a/dataengines/powermanagement/powermanagementjob.h b/dataengines/powermanagement/powermanagementjob.h --- a/dataengines/powermanagement/powermanagementjob.h +++ b/dataengines/powermanagement/powermanagementjob.h @@ -22,9 +22,10 @@ // plasma #include +class QDBusPendingCall; + class PowerManagementJob : public Plasma::ServiceJob { - Q_OBJECT public: @@ -37,8 +38,8 @@ private: void requestShutDown(); - void setScreenBrightness(int value, bool silent); - void setKeyboardBrightness(int value, bool silent); + QDBusPendingCall setScreenBrightness(int value, bool silent); + QDBusPendingCall setKeyboardBrightness(int value, bool silent); }; #endif // POWERMANAGEMENTJOB_H diff --git a/dataengines/powermanagement/powermanagementjob.cpp b/dataengines/powermanagement/powermanagementjob.cpp --- a/dataengines/powermanagement/powermanagementjob.cpp +++ b/dataengines/powermanagement/powermanagementjob.cpp @@ -41,6 +41,16 @@ { } +static void callWhenFinished(const QDBusPendingCall& pending, std::function func, QObject* parent) +{ + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pending, parent); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, + parent, [func](QDBusPendingCallWatcher* watcher) { + watcher->deleteLater(); + func(); + }); +} + void PowerManagementJob::start() { const QString operation = operationName(); @@ -92,37 +102,37 @@ setResult(Solid::PowerManagement::stopSuppressingScreenPowerManagement(parameters().value(QStringLiteral("cookie")).toInt())); return; } else if (operation == QLatin1String("setBrightness")) { - setScreenBrightness(parameters().value(QStringLiteral("brightness")).toInt(), parameters().value(QStringLiteral("silent")).toBool()); - setResult(true); + auto pending = setScreenBrightness(parameters().value(QStringLiteral("brightness")).toInt(), parameters().value(QStringLiteral("silent")).toBool()); + callWhenFinished(pending, [this] { setResult(true); }, this); return; } else if (operation == QLatin1String("setKeyboardBrightness")) { - setKeyboardBrightness(parameters().value(QStringLiteral("brightness")).toInt(), parameters().value(QStringLiteral("silent")).toBool()); - setResult(true); + auto pending = setKeyboardBrightness(parameters().value(QStringLiteral("brightness")).toInt(), parameters().value(QStringLiteral("silent")).toBool()); + callWhenFinished(pending, [this] { setResult(true); }, this); return; } qDebug() << "don't know what to do with " << operation; setResult(false); } -void PowerManagementJob::setScreenBrightness(int value, bool silent) +QDBusPendingCall PowerManagementJob::setScreenBrightness(int value, bool silent) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement/Actions/BrightnessControl"), QStringLiteral("org.kde.Solid.PowerManagement.Actions.BrightnessControl"), silent ? "setBrightnessSilent" : "setBrightness"); msg << value; - QDBusConnection::sessionBus().asyncCall(msg); + return QDBusConnection::sessionBus().asyncCall(msg); } -void PowerManagementJob::setKeyboardBrightness(int value, bool silent) +QDBusPendingCall PowerManagementJob::setKeyboardBrightness(int value, bool silent) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement/Actions/KeyboardBrightnessControl"), QStringLiteral("org.kde.Solid.PowerManagement.Actions.KeyboardBrightnessControl"), silent ? "setKeyboardBrightnessSilent" : "setKeyboardBrightness"); msg << value; - QDBusConnection::sessionBus().asyncCall(msg); + return QDBusConnection::sessionBus().asyncCall(msg); } void PowerManagementJob::requestShutDown() diff --git a/dataengines/share/CMakeLists.txt b/dataengines/share/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -add_definitions(-DTRANSLATION_DOMAIN=\"plasma_engine_share\") - -set(share_engine_SRCS - shareprovider.cpp - shareengine.cpp - shareservice.cpp) - -add_library(plasma_engine_share MODULE ${share_engine_SRCS}) -target_link_libraries(plasma_engine_share - KF5::Plasma - KF5::KIOCore - KF5::JsEmbed -) -kcoreaddons_desktop_to_json(plasma_engine_share plasma-dataengine-share.desktop) - -install(TARGETS plasma_engine_share - DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/dataengine) - -install(FILES plasma-dataengine-share.desktop - DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) - -install(FILES data/plasma_shareprovider.desktop - DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) - -install(FILES share.operations - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/services) - -add_subdirectory(packagestructure) -add_subdirectory(backends) diff --git a/dataengines/share/Messages.sh b/dataengines/share/Messages.sh deleted file mode 100755 --- a/dataengines/share/Messages.sh +++ /dev/null @@ -1,2 +0,0 @@ -#! /usr/bin/env bash -$XGETTEXT *.cpp -o $podir/plasma_engine_share.pot diff --git a/dataengines/share/backends/CMakeLists.txt b/dataengines/share/backends/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_subdirectory(kde) -add_subdirectory(imgur) -add_subdirectory(pastebincom) -add_subdirectory(pasteubuntucom) -add_subdirectory(simplestimagehosting) -add_subdirectory(wklej) -add_subdirectory(wstaw) -add_subdirectory(pasteopensuseorg) -add_subdirectory(imgsusepasteorg) -add_subdirectory(privatepaste) -add_subdirectory(im9) diff --git a/dataengines/share/backends/im9/CMakeLists.txt b/dataengines/share/backends/im9/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/im9/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-im9.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-im9.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/im9/) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/im9) diff --git a/dataengines/share/backends/im9/contents/code/main.js b/dataengines/share/backends/im9/contents/code/main.js deleted file mode 100644 --- a/dataengines/share/backends/im9/contents/code/main.js +++ /dev/null @@ -1,47 +0,0 @@ -/********************************************************************************** -* im9.eu backend for Plasma. -* Copyright (C) 2012 Michal Dutkiewicz -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public License -* as published by the Free Software Foundation; either version 2 -* of the License, or (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -* -**********************************************************************************/ - -function url() -{ - return 'http://api.im9.eu/kde-pastebin/'; -} - -function contentKey() -{ - return 'image'; -} - -function setup() -{ -} - -function handleResultData(data) -{ - var result = data.match('800\n(http://.+)\n'); - - if (result == '') - { - provider.error(data); - - return; - } - - provider.success(data.replace('800', '').replace('\n', '')); -} diff --git a/dataengines/share/backends/im9/metadata.desktop b/dataengines/share/backends/im9/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/im9/metadata.desktop +++ /dev/null @@ -1,108 +0,0 @@ -[Desktop Entry] -Name=im9.eu -Name[bs]=im9.eu -Name[ca]=im9.eu -Name[ca@valencia]=im9.eu -Name[cs]=im9.eu -Name[da]=im9.eu -Name[de]=im9.eu -Name[el]=im9.eu -Name[en_GB]=im9.eu -Name[es]=im9.eu -Name[et]=im9.eu -Name[eu]=im9.eu -Name[fi]=im9.eu -Name[fr]=im9.eu -Name[gl]=im9.eu -Name[he]=im9.eu -Name[hu]=im9.eu -Name[ia]=im9.eu -Name[id]=im9.eu -Name[is]=im9.eu -Name[it]=im9.eu -Name[ja]=im9.eu -Name[kk]=im9.eu -Name[ko]=im9.eu -Name[lt]=im9.eu -Name[nb]=im9.eu -Name[nds]=im9.eu -Name[nl]=im9.eu -Name[nn]=im9.eu -Name[pa]=im9.eu -Name[pl]=im9.eu -Name[pt]=im9.eu -Name[pt_BR]=im9.eu -Name[ro]=im9.eu -Name[ru]=im9.eu -Name[sk]=im9.eu -Name[sl]=im9.eu -Name[sr]=им9 -Name[sr@ijekavian]=им9 -Name[sr@ijekavianlatin]=im9 -Name[sr@latin]=im9 -Name[sv]=im9.eu -Name[tr]=im9.eu -Name[uk]=im9.eu -Name[x-test]=xxim9.euxx -Name[zh_CN]=im9.eu -Name[zh_TW]=im9.eu -Comment=Allows images to be shared using the im9.eu service -Comment[ar]=يسمح بمشاركة الصور عبر خدمة im9.eu -Comment[bs]=Dopušta dijeljenje slika preko servisa im9.eu -Comment[ca]=Permet la compartició d'imatges utilitzant el servei im9.eu -Comment[ca@valencia]=Permet la compartició d'imatges utilitzant el servei im9.eu -Comment[cs]=Povolí sdílení obrázků pomocí služby im9.eu -Comment[da]=Muliggør deling af billeder med tjenesten im9.eu -Comment[de]=Ermöglicht die Veröffentlichung von Bildern über den im9.eu-Dienst -Comment[el]=Επιτρέπει το μοίρασμα των εικόνων με την υπηρεσία im9.eu -Comment[en_GB]=Allows images to be shared using the im9.eu service -Comment[es]=Permite compartir imágenes a través del servicio im9.eu -Comment[et]=Piltide jagamine im9.eu teenuse kaudu -Comment[eu]=Irudiak im9.eu zerbitzuaren bidez partekatzeko aukera ematen du -Comment[fi]=Mahdollistaa kuvien jakamisen im9.eu-palvelussa -Comment[fr]=Permet le partage d'images à l'aide du service « im9.eu » -Comment[gl]=Permite compartir imaxes mediante o servizo im9.eu -Comment[he]=מאפשר שיתוף תמונות באמצעות השירות im9.eu -Comment[hu]=Képek megosztása az im9.eu szolgáltatás használatával -Comment[ia]=Il permitte imagines de esser compartite per usage de servicio im9.eu -Comment[id]=Memungkinkan berbagi gambar menggunakan layanan im9.eu -Comment[is]=Gerir kleift að deila myndum með im9.eu þjónustunni -Comment[it]=Abilita la condivisione di immagini con il servizio img9.eu -Comment[ja]=imgur のサービスを用いた画像の共有を有効にする -Comment[kk]=im9.eu қызметі көмегімен кескіндерді ортақ қылуды рұқсат ету -Comment[ko]=im9.eu로 그림을 공유합니다 -Comment[lt]=Įjungia dalinimąsi nuotraukomis naudojant im9.eu tarnybą -Comment[nb]=Gjør det mulig å dele bilder via tjenesten im9.eu -Comment[nds]=Lett Di Biller över den Deenst "im9.eu" praatstellen -Comment[nl]=Staat het delen van afbeeldingen toe met gebruik van de service im9.eu -Comment[nn]=Gjer det mogleg å dela bilete via tenesta im9.eu -Comment[pa]=im9.eu ਸਰਵਿਸ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਚਿੱਤਰ ਸਾਂਝੇ ਕਰਨ ਲਈ ਸਹਾਇਕ -Comment[pl]=Udostępniania obrazy przy użyciu usługi im9.eu -Comment[pt]=Permite a partilha de imagens com o serviço do 'im9.eu' -Comment[pt_BR]=Permite o compartilhamento de imagens usando o serviço do im9.eu -Comment[ro]=Permite partajarea imaginilor cu serviciul im9.eu -Comment[ru]=Позволяет публиковать изображения на сервере im9.eu -Comment[sk]=Povolí zdieľanie obrázkov pomocou služby im9.eu -Comment[sl]=Omogoča deljenje slik prek storitve im9.eu -Comment[sr]=Дељење слика преко сервиса им9 -Comment[sr@ijekavian]=Дијељење слика преко сервиса им9 -Comment[sr@ijekavianlatin]=Dijeljenje slika preko servisa im9 -Comment[sr@latin]=Deljenje slika preko servisa im9 -Comment[sv]=Tillåter att bilder delas genom att använda tjänsten im9.eu -Comment[tr]=im9.eu hizmetini kullanarak resimleri paylaşmayı sağlar -Comment[uk]=Уможливлює оприлюднення зображень за допомогою служби im9.eu -Comment[x-test]=xxAllows images to be shared using the im9.eu servicexx -Comment[zh_CN]=用 im9.eu 服务共享图片 -Comment[zh_TW]=允許使用 im9.eu 服務分享影像 -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=image/* - -X-KDE-PluginInfo-Name=im9 -X-KDE-PluginInfo-Author=Michał Dutkiewicz -X-KDE-PluginInfo-Email=emdeck@gmail.com -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop - diff --git a/dataengines/share/backends/imgsusepasteorg/CMakeLists.txt b/dataengines/share/backends/imgsusepasteorg/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/imgsusepasteorg/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-imgsusepasteorg.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-imgsusepasteorg.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/imgsusepasteorg) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/imgsusepasteorg) diff --git a/dataengines/share/backends/imgsusepasteorg/contents/code/main.js b/dataengines/share/backends/imgsusepasteorg/contents/code/main.js deleted file mode 100644 --- a/dataengines/share/backends/imgsusepasteorg/contents/code/main.js +++ /dev/null @@ -1,47 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2010 by Will Stephenson * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * - ***************************************************************************/ - -function url() { - return "http://susepaste.org"; -} - -function contentKey() { - return "file"; -} - -function setup() { - provider.addPostItem("name", "KDE", "text/plain"); - provider.addPostItem("title", "mypaste", "text/plain"); - provider.addPostItem("lang", "image", "text/plain"); - provider.addPostItem("submit", "submit", "text/plain"); - provider.addPostItem("expire","1440","text/plain"); -} - -function handleResultData(data) { - console.log("--> data: " + data); - var res = data.match("(Info.+)"); - if (res != "") { - return; - } - provider.error(data); -} - -function handleRedirection(url) { - provider.success(url); -} diff --git a/dataengines/share/backends/imgsusepasteorg/metadata.desktop b/dataengines/share/backends/imgsusepasteorg/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/imgsusepasteorg/metadata.desktop +++ /dev/null @@ -1,124 +0,0 @@ -[Desktop Entry] -Name=img.susepaste.org -Name[bg]=img.susepaste.org -Name[bs]=img.susepaste.org -Name[ca]=img.susepaste.org -Name[ca@valencia]=img.susepaste.org -Name[cs]=img.susepaste.org -Name[da]=img.susepaste.org -Name[de]=img.susepaste.org -Name[el]=img.susepaste.org -Name[en_GB]=img.susepaste.org -Name[es]=img.susepaste.org -Name[et]=img.susepaste.org -Name[eu]=img.susepaste.org -Name[fi]=img.susepaste.org -Name[fr]=img.susepaste.org -Name[gl]=img.susepaste.org -Name[he]=img.susepaste.org -Name[hr]=img.susepaste.org -Name[hu]=img.susepaste.org -Name[ia]=img.susepaste.org -Name[id]=img.susepaste.org -Name[is]=img.susepaste.org -Name[it]=img.susepaste.org -Name[ja]=img.susepaste.org -Name[kk]=img.susepaste.org -Name[km]=img.susepaste.org -Name[ko]=img.susepaste.org -Name[lt]=img.susepaste.org -Name[lv]=img.susepaste.org -Name[mr]=img.susepaste.org -Name[nb]=img.susepaste.org -Name[nds]=img.susepaste.org -Name[nl]=img.susepaste.org -Name[nn]=img.susepaste.org -Name[pa]=img.susepaste.org -Name[pl]=img.susepaste.org -Name[pt]=img.susepaste.org -Name[pt_BR]=img.susepaste.org -Name[ro]=img.susepaste.org -Name[ru]=img.susepaste.org -Name[sk]=img.susepaste.org -Name[sl]=img.susepaste.org -Name[sr]=img.susepaste.org -Name[sr@ijekavian]=img.susepaste.org -Name[sr@ijekavianlatin]=img.susepaste.org -Name[sr@latin]=img.susepaste.org -Name[sv]=img.susepaste.org -Name[tr]=img.susepaste.org -Name[ug]=img.susepaste.org -Name[uk]=img.susepaste.org -Name[wa]=img.susepaste.org -Name[x-test]=xximg.susepaste.orgxx -Name[zh_CN]=img.susepaste.org -Name[zh_TW]=img.susepaste.org -Comment=Allows images to be shared using the susepaste.org service -Comment[ar]=يسمح بمشاركة الصور عبر خدمة susepaste.org -Comment[bg]=Позволява споделянето на картинки чрез susepaste.org -Comment[bs]=Dopušta dijeljenje slika preko servisa susepaste.org -Comment[ca]=Permet la compartició d'imatges utilitzant el servei susepaste.org -Comment[ca@valencia]=Permet la compartició d'imatges utilitzant el servei susepaste.org -Comment[cs]=Povolí sdílení obrázků pomocí služby susepaste.org -Comment[da]=Muliggør deling af billeder med tjenesten susepaste.org -Comment[de]=Ermöglicht die Veröffentlichung von Bildern über den susepaste.org-Dienst -Comment[el]=Επιτρέπει το μοίρασμα των εικόνων χρησιμοποιώντας την υπηρεσία susepaste.org -Comment[en_GB]=Allows images to be shared using the susepaste.org service -Comment[es]=Permite compartir imágenes a través del servicio de susepaste.org -Comment[et]=Piltide jagamine susepaste.org teenuse kaudu -Comment[eu]=Irudiak susepaste.org zerbitzuaren bidez partekatzeko aukera ematen du -Comment[fi]=Mahdollistaa kuvien jakamisen susepaste.org-palvelussa -Comment[fr]=Permet le partage d'images à l'aide du service « susepaste.org » -Comment[gl]=Permite compartir imaxes mediante o servizo susepaste.org -Comment[he]=מאפשר שיתוף תמונות באמצעות השירות susepaste.org -Comment[hr]=Omogućuje dijeljenje slika koristeći servis susepaste.org -Comment[hu]=Képek megosztása a susepaste.org szolgáltatás használatával -Comment[ia]=Il permitte images de esser compartite per usar le servicio susepaste.org -Comment[id]=Memungkinkan berbagi gambar menggunakan layanan susepaste.org -Comment[is]=Gerir kleift að deila myndum með susepaste.org þjónustunni -Comment[it]=Abilita la condivisione di immagini con il servizio susepaste.org -Comment[ja]=susepaste.org のサービスを用いた画像の共有を有効にする -Comment[kk]=susepaste.org қызметі көмегімен кескіндерді ортақ қылуды рұқсат ету -Comment[km]=បើក​ការ​ចែករំលែក​រូបភាព​ ដោយ​ប្រើ​សេវា susepaste.org -Comment[ko]=susepaste.org로 그림을 공유합니다 -Comment[lt]=Įjungia dalinimąsį nuotraukomis naudojant susepaste.org tarnybą -Comment[lv]=Ļauj kopīgot attēlus, izmantojot susepaste.org servisu -Comment[mr]=susepaste.org सेवा वापरुन प्रतिमा शेअर करतो -Comment[nb]=Gjør det mulig å dele bilder via tjenesten susepaste.org -Comment[nds]=Biller op "img.susepaste.org" praatstellen -Comment[nl]=Staat het delen van afbeeldingen toe met gebruik van de service susepaste.org -Comment[nn]=Gjer det mogleg å dela bilete via tenesta susepaste.org -Comment[pa]=susepaste.org ਸਰਵਿਸ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਚਿੱਤਰ ਸਾਂਝੇ ਕਰਨ ਲਈ ਸਹਾਇਕ -Comment[pl]=Udostępniania obrazy przy użyciu usługi susepaste.org -Comment[pt]=Permite a partilha de imagens com o serviço do 'susepaste.org' -Comment[pt_BR]=Permite o compartilhamento de imagens usando o serviço do susepaste.org -Comment[ro]=Permite partajarea imaginilor cu serviciul susepaste.org -Comment[ru]=Позволяет публиковать изображения на сервере susepaste.org -Comment[sk]=Povolí zdieľanie obrázkov pomocou služby susepaste.org -Comment[sl]=Omogoča deljenje slik prek storitve susepaste.org -Comment[sr]=Дељење слика преко сервиса susepaste.org -Comment[sr@ijekavian]=Дијељење слика преко сервиса susepaste.org -Comment[sr@ijekavianlatin]=Dijeljenje slika preko servisa susepaste.org -Comment[sr@latin]=Deljenje slika preko servisa susepaste.org -Comment[sv]=Tillåter att bilder delas genom att använda tjänsten susepaste.org -Comment[tr]=susepaste.org hizmeti kullanarak resim paylaşımı sağlar -Comment[ug]=رەسىملەرنى susepaste.org مۇلازىمىتىنى ئىشلىتىپ ھەمبەھىرلەشكە ئىجازەت -Comment[uk]=Уможливлює оприлюднення зображень за допомогою служби susepaste.org -Comment[vi]=Cho phép chia sẻ ảnh qua dịch vụ susepaste.org -Comment[wa]=Permete li pårtaedje d' imådjes e s' siervant do siervice susepaste.org -Comment[x-test]=xxAllows images to be shared using the susepaste.org servicexx -Comment[zh_CN]=用 susepaste.org 服务共享图片 -Comment[zh_TW]=允許使用 susepaste.org 服務分享影像 -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=image/* - -X-KDE-PluginInfo-Name=imgsusepasteorg -X-KDE-PluginInfo-Author=Javier Llorente -X-KDE-PluginInfo-Email=javier@opensuse.org -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop -X-KDE-Priority=1 - diff --git a/dataengines/share/backends/imgur/CMakeLists.txt b/dataengines/share/backends/imgur/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/imgur/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-imgur.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-imgur.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/imgur/) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/imgur) diff --git a/dataengines/share/backends/imgur/contents/code/main.js b/dataengines/share/backends/imgur/contents/code/main.js deleted file mode 100644 --- a/dataengines/share/backends/imgur/contents/code/main.js +++ /dev/null @@ -1,41 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2010 by Artur Duque de Souza * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * - ***************************************************************************/ - -function url() { - return "http://imgur.com/api/upload"; -} - -function contentKey() { - return "image"; -} - -function setup() { - // key associated with plasma-devel@kde.org - // thanks to Alan Schaaf of Imgur (alan@imgur.com) - provider.addPostItem("key", "d0757bc2e94a0d4652f28079a0be9379", "text/plain"); -} - -function handleResultData(data) { - var res = provider.parseXML("original_image", data); - if (res == "") { - provider.error(data); - return; - } - provider.success(res); -} diff --git a/dataengines/share/backends/imgur/metadata.desktop b/dataengines/share/backends/imgur/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/imgur/metadata.desktop +++ /dev/null @@ -1,128 +0,0 @@ -[Desktop Entry] -Name=Imgur -Name[bg]=Imgur -Name[bs]=Imgur -Name[ca]=Imgur -Name[ca@valencia]=Imgur -Name[cs]=Imgur -Name[da]=Imgur -Name[de]=Imgur -Name[el]=Imgur -Name[en_GB]=Imgur -Name[es]=Imgur -Name[et]=Imgur -Name[eu]=Imgur -Name[fi]=Imgur -Name[fr]=Imgur -Name[ga]=Imgur -Name[gl]=Imgur -Name[he]=Imgur -Name[hi]=इंगुर -Name[hr]=Imgur -Name[hu]=Imgur -Name[ia]=Imgur -Name[id]=Imgur -Name[is]=Imgur -Name[it]=Imgur -Name[ja]=Imgur -Name[kk]=Imgur -Name[km]=Imgur -Name[kn]=Imgur -Name[ko]=Imgur -Name[lt]=Imgur -Name[lv]=Imgur -Name[mr]=Imgur -Name[nb]=Imgur -Name[nds]=imgur -Name[nl]=Imgur -Name[nn]=Imgur -Name[pa]=Imgur -Name[pl]=Imgur -Name[pt]=Imgur -Name[pt_BR]=Imgur -Name[ro]=Imgur -Name[ru]=Imgur -Name[sk]=Imgur -Name[sl]=Imgur -Name[sr]=Имгур -Name[sr@ijekavian]=Имгур -Name[sr@ijekavianlatin]=Imgur -Name[sr@latin]=Imgur -Name[sv]=Imgur -Name[th]=Imgur -Name[tr]=Imgur -Name[ug]=Imgur -Name[uk]=Imgur -Name[vi]=Imgur -Name[wa]=Imgur -Name[x-test]=xxImgurxx -Name[zh_CN]=Imgur -Name[zh_TW]=Imgur -Comment=Allows images to be shared using the imgur service -Comment[ar]=يسمح بمشاركة الصور عبر خدمة imgur -Comment[bg]=Позволява споделянето на картинки чрез imgur -Comment[bs]=Dopušta dijeljenje slika preko servisa Imgur -Comment[ca]=Permet la compartició d'imatges utilitzant el servei imgur -Comment[ca@valencia]=Permet la compartició d'imatges utilitzant el servei imgur -Comment[cs]=Povolí sdílení obrázků pomocí služby imgur -Comment[da]=Muliggør deling af billeder med tjenesten imgur -Comment[de]=Ermöglicht die Veröffentlichung von Bildern über den Imgur-Dienst -Comment[el]=Επιτρέπει το μοίρασμα των εικόνων χρησιμοποιώντας την υπηρεσία imgur -Comment[en_GB]=Allows images to be shared using the imgur service -Comment[es]=Permite compartir imágenes a través del servicio de imgur -Comment[et]=Piltide jagamine imguri teenuse kaudu -Comment[eu]=Irudiak imgur zerbitzuaren bidez partekatzeko aukera ematen du -Comment[fi]=Mahdollistaa kuvien jakamisen imgur-palvelussa -Comment[fr]=Permet le partage d'images à l'aide du service « imgur » -Comment[gl]=Permite compartir imaxes mediante o servizo imgur -Comment[he]=מאפשר שיתוף תמונות באמצעות השירות imgur -Comment[hr]=Omogućuje dijeljenje slika koristeći servis imgur -Comment[hu]=Képek megosztása az imgur szolgáltatás használatával -Comment[ia]=Il permitte imagines de esser compartite per usage de servicio imgur -Comment[id]=Memungkinkan gambar dibagi menggunakan layanan imgur -Comment[is]=Gerir kleift að deila myndum með imgur þjónustunni -Comment[it]=Abilita la condivisione di immagini con il servizio imgur -Comment[ja]=imgur のサービスを用いた画像の共有を有効にする -Comment[kk]=Imgur қызметі көмегімен кескіндерді ортақ қылуды рұқсат ету -Comment[km]=បើក​ការ​ចែករំលែក​រូបភាព ដោយ​ប្រើ​សេវា imgur​​ -Comment[ko]=imgur로 그림을 공유합니다 -Comment[lt]=Įjungia dalinimąsį nuotraukomis naudojant imgur tarnybą -Comment[lv]=Ļauj kopīgot attēlus, izmantojot imgur servisu -Comment[mr]=imgur सेवा वापरुन प्रतिमा शेअर करतो -Comment[nb]=Gjør det mulig å dele bilder via tjenesten imgur -Comment[nds]=Biller op "imgur" praatstellen -Comment[nl]=Staat het delen van afbeeldingen toe met gebruik van de service imgur -Comment[nn]=Gjer det mogleg å dela bilete via tenesta imgur -Comment[pa]=imgur ਸਰਵਿਸ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਚਿੱਤਰ ਸਾਂਝੇ ਕਰਨ ਲਈ ਸਹਾਇਕ -Comment[pl]=Udostępniania obrazy przy użyciu usługi imgur -Comment[pt]=Permite a partilha de imagens com o serviço do 'imgur' -Comment[pt_BR]=Permite o compartilhamento de imagens usando o serviço do imgur -Comment[ro]=Permite partajarea imaginilor cu serviciul imgur -Comment[ru]=Позволяет публиковать изображения на сервере imgur.com -Comment[sk]=Povolí zdieľanie obrázkov pomocou služby imgur -Comment[sl]=Omogoča deljenje slik prek storitve imgur -Comment[sr]=Дељење слика преко сервиса Имгур -Comment[sr@ijekavian]=Дијељење слика преко сервиса Имгур -Comment[sr@ijekavianlatin]=Dijeljenje slika preko servisa Imgur -Comment[sr@latin]=Deljenje slika preko servisa Imgur -Comment[sv]=Tillåter att bilder delas genom att använda tjänsten imgur -Comment[tr]=imgur servisini kullanarak resimleri paylaşmayı sağlar -Comment[ug]=رەسىملەرنى imgur مۇلازىمىتىنى ئىشلىتىپ ھەمبەھىرلەشكە ئىجازەت -Comment[uk]=Уможливлює оприлюднення зображень за допомогою служби imgur -Comment[vi]=Cho phép chia sẻ ảnh qua dịch vụ imgur -Comment[wa]=Permete li pårtaedje d' imådjes e s' siervant do siervice imgur -Comment[x-test]=xxAllows images to be shared using the imgur servicexx -Comment[zh_CN]=用 Imgur 服务共享图片 -Comment[zh_TW]=允許使用 imgur 服務分享影像 -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=image/* - -X-KDE-PluginInfo-Name=imgur -X-KDE-PluginInfo-Author=Artur de Souza -X-KDE-PluginInfo-Email=asouza@kde.org -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop - diff --git a/dataengines/share/backends/kde/CMakeLists.txt b/dataengines/share/backends/kde/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/kde/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-kde.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-kde.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/kde/) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/kde) diff --git a/dataengines/share/backends/kde/metadata.desktop b/dataengines/share/backends/kde/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/kde/metadata.desktop +++ /dev/null @@ -1,118 +0,0 @@ -[Desktop Entry] -Name=paste.kde.org -Name[bs]=paste.kde.org -Name[ca]=paste.kde.org -Name[ca@valencia]=paste.kde.org -Name[cs]=paste.kde.org -Name[da]=paste.kde.org -Name[de]=paste.kde.org -Name[el]=paste.kde.org -Name[en_GB]=paste.kde.org -Name[es]=paste.kde.org -Name[et]=paste.kde.org -Name[eu]=paste.kde.org -Name[fi]=paste.kde.org -Name[fr]=paste.kde.org -Name[gl]=paste.kde.org -Name[he]=paste.kde.org -Name[hu]=paste.kde.org -Name[ia]=paste.kde.org -Name[id]=paste.kde.org -Name[is]=paste.kde.org -Name[it]=paste.kde.org -Name[ja]=paste.kde.org -Name[kk]=paste.kde.org -Name[ko]=paste.kde.org -Name[lt]=paste.kde.org -Name[mr]=paste.kde.org -Name[nb]=paste.kde.org -Name[nds]=paste.kde.org -Name[nl]=paste.kde.org -Name[nn]=paste.kde.org -Name[pa]=paste.kde.org -Name[pl]=paste.kde.org -Name[pt]=paste.kde.org -Name[pt_BR]=paste.kde.org -Name[ro]=paste.kde.org -Name[ru]=paste.kde.org -Name[sk]=paste.kde.org -Name[sl]=paste.kde.org -Name[sr]=paste.kde.org -Name[sr@ijekavian]=paste.kde.org -Name[sr@ijekavianlatin]=paste.kde.org -Name[sr@latin]=paste.kde.org -Name[sv]=paste.kde.org -Name[tr]=paste.kde.org -Name[uk]=paste.kde.org -Name[x-test]=xxpaste.kde.orgxx -Name[zh_CN]=paste.kde.org -Name[zh_TW]=paste.kde.org -Comment=Allows text to be shared using the kde.org service -Comment[ar]=يسمح بمشاركة النصوص عبر خدمة kde.org -Comment[bg]=Позволява споделянето на текст чрез kde.org -Comment[bs]=Dopušta ijeljenje teksta preko servisa wklej.org -Comment[ca]=Permet la compartició de text utilitzant el servei kde.org -Comment[ca@valencia]=Permet la compartició de text utilitzant el servei kde.org -Comment[cs]=Povolí sdílení textů pomocí služby kde.org -Comment[da]=Muliggør deling af tekst med tjenesten kde.org -Comment[de]=Ermöglicht die Veröffentlichung von Text über den kde.org-Dienst -Comment[el]=Επιτρέπει στο κείμενο να γίνει κοινόχρηστο χρησιμοποιώντας την υπηρεσία kde.org -Comment[en_GB]=Allows text to be shared using the kde.org service -Comment[es]=Permite compartir texto a través del servicio de kde.org -Comment[et]=Teksti jagamine kde.org teenuse kaudu -Comment[eu]=Testua kde.org zerbitzuaren bidez partekatzeko aukera ematen du -Comment[fi]=Mahdollistaa tekstin jakamisen kde.org-palvelussa -Comment[fr]=Permet le partage de texte à l'aide du service « kde.org » -Comment[gl]=Permite compartir texto mediante o servizo kde.org -Comment[he]=מאפשר שיתוף טקסט באמצעות השירות kde.org -Comment[hr]=Omogućuje dijeljenje teksta koristeći servis kde.org -Comment[hu]=Szöveg megosztása a kde.org szolgáltatás használatával -Comment[ia]=Il permitte texto de esser compartite per usar le servicio kde.org -Comment[id]=Memungkinkan berbagi teks menggunakan layanan kde.org -Comment[is]=Gerir kleift að deila texta með kde.org límklippusafninu -Comment[it]=Abilita la condivisione di testo con il servizio kde.org -Comment[ja]=kde.org のサービスを用いたテキストの共有を有効にする -Comment[kk]=kde.org қызметі көмегімен мәтінді ортақ қылуды рұқсат ету -Comment[km]=បើក​ការ​ចែករំលែក​អត្ថបទ​ដោយ​ប្រើ​សេវា kde.org -Comment[ko]=kde.org로 텍스트를 공유합니다 -Comment[lt]=Įjungia dalinimąsį tekstu naudojant kde.org tarnybą -Comment[lv]=Ļauj kopīgot tekstu, izmantojot kde.org servisu -Comment[mr]=kde.org सेवा वापरुन पाठ्य शेअर करतो -Comment[nb]=Gjør det mulig å dele tekst via tjenesten kde.org -Comment[nds]=Text op "kde.org" praatstellen -Comment[nl]=Staat het delen van tekst toe met gebruik van de service kde.org -Comment[nn]=Gjer det mogleg å dela tekst via tenesta kde.org -Comment[pa]=kde.org ਸਰਵਿਸ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਟੈਕਸਟ ਸਾਂਝਾ ਕਰਨਾ ਯੋਗ -Comment[pl]=Udostępniania tekst przy użyciu usługi kde.org -Comment[pt]=Permite a partilha de texto com o serviço do 'kde.org' -Comment[pt_BR]=Permite o compartilhamento de texto usando o serviço do kde.org -Comment[ro]=Permite partajarea textului cu serviciul kde.org -Comment[ru]=Позволяет публиковать текст на сервере kde.org -Comment[sk]=Povolí zdieľanie textu pomocou služby kde.org -Comment[sl]=Omogoča deljenje besedila prek storitve kde.org -Comment[sr]=Дељење текста преко сервиса kde.org -Comment[sr@ijekavian]=Дијељење текста преко сервиса kde.org -Comment[sr@ijekavianlatin]=Dijeljenje teksta preko servisa kde.org -Comment[sr@latin]=Deljenje teksta preko servisa kde.org -Comment[sv]=Tillåter att text delas genom att använda tjänsten kde.org -Comment[tr]=kde.org servisini kullanarak metinleri paylaşmayı sağlar -Comment[ug]=تېكىستلەرنى kde.org مۇلازىمىتىنى ئىشلىتىپ ھەمبەھىرلەشكە ئىجازەت -Comment[uk]=Уможливлює оприлюднення фрагментів тексту за допомогою служби kde.org -Comment[vi]=Cho phép chia sẻ văn bản qua dịch vụ kde.org -Comment[wa]=Permete li pårtaedje di scrijhaedje e s' siervant do siervice kde.org -Comment[x-test]=xxAllows text to be shared using the kde.org servicexx -Comment[zh_CN]=用 kde.org 服务共享文字 -Comment[zh_TW]=允許使用 kde.org 服務分享文字 -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=text/* - -X-KDE-PluginInfo-Name=kde -X-KDE-PluginInfo-Author=Artur de Souza -X-KDE-PluginInfo-Email=asouza@kde.org -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop -X-KDE-Priority=100 - diff --git a/dataengines/share/backends/pastebincom/CMakeLists.txt b/dataengines/share/backends/pastebincom/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/pastebincom/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-pastebincom.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-pastebincom.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/pastebincom/) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/pastebincom) diff --git a/dataengines/share/backends/pastebincom/metadata.desktop b/dataengines/share/backends/pastebincom/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/pastebincom/metadata.desktop +++ /dev/null @@ -1,128 +0,0 @@ -[Desktop Entry] -Name=pastebin.com -Name[bg]=pastebin.com -Name[bs]=pastebin.com -Name[ca]=pastebin.com -Name[ca@valencia]=pastebin.com -Name[cs]=pastebin.com -Name[da]=pastebin.com -Name[de]=pastebin.com -Name[el]=pastebin.com -Name[en_GB]=pastebin.com -Name[es]=pastebin.com -Name[et]=pastebin.com -Name[eu]=pastebin.com -Name[fi]=pastebin.com -Name[fr]=pastebin.com -Name[ga]=pastebin.com -Name[gl]=pastebin.com -Name[gu]=pastebin.com -Name[he]=pastebin.com -Name[hi]=pastebin.com -Name[hr]=pastebin.com -Name[hu]=pastebin.com -Name[ia]=pastebin.com -Name[id]=pastebin.com -Name[is]=pastebin.com -Name[it]=pastebin.com -Name[ja]=pastebin.com -Name[kk]=pastebin.com -Name[km]=pastebin.com -Name[ko]=pastebin.com -Name[lt]=pastebin.com -Name[lv]=pastebin.com -Name[mr]=pastebin.com -Name[nb]=pastebin.com -Name[nds]=pastebin.com -Name[nl]=pastebin.com -Name[nn]=pastebin.com -Name[pa]=pastebin.com -Name[pl]=pastebin.com -Name[pt]=pastebin.com -Name[pt_BR]=pastebin.com -Name[ro]=pastebin.com -Name[ru]=pastebin.com -Name[sk]=pastebin.com -Name[sl]=pastebin.com -Name[sr]=pastebin.com -Name[sr@ijekavian]=pastebin.com -Name[sr@ijekavianlatin]=pastebin.com -Name[sr@latin]=pastebin.com -Name[sv]=pastebin.com -Name[tg]=pastebin.com -Name[th]=pastebin.com -Name[tr]=pastebin.com -Name[ug]=pastebin.com -Name[uk]=pastebin.com -Name[wa]=pastebin.com -Name[x-test]=xxpastebin.comxx -Name[zh_CN]=pastebin.com -Name[zh_TW]=pastebin.com -Comment=Allows text to be shared using the pastebin.com service -Comment[ar]=يسمح بمشاركة النصوص عبر خدمة pastebin.com -Comment[bg]=Позволява споделянето на текст чрез pastebin.com -Comment[bs]=Omogućava dijeljenje teksta preko servisa pastebin.com -Comment[ca]=Permet la compartició de text utilitzant el servei pastebin.com -Comment[ca@valencia]=Permet la compartició de text utilitzant el servei pastebin.com -Comment[cs]=Povolí sdílení textů pomocí služby pastebin.com -Comment[da]=Muliggør deling af tekst med tjenesten pastebin.com -Comment[de]=Ermöglicht die Veröffentlichung von Text über den pastebin.com-Dienst -Comment[el]=Επιτρέπει στο κείμενο να γίνει κοινόχρηστο χρησιμοποιώντας την υπηρεσία pastebin.com -Comment[en_GB]=Allows text to be shared using the pastebin.com service -Comment[es]=Permite compartir texto a través del servicio de pastebin.com -Comment[et]=Teksti jagamine pastebin.com teenuse kaudu -Comment[eu]=Testua pastebin.com zerbitzuaren bidez partekatzeko aukera ematen du -Comment[fi]=Mahdollistaa tekstin jakamisen pastebin.com-palvelussa -Comment[fr]=Permet le partage de texte à l'aide du service « pastebin.com » -Comment[gl]=Permite compartir texto mediante o servizo pastebin.org -Comment[he]=מאפשר שיתוף טקסט באמצעות השירות pastebin.com -Comment[hr]=Omogućuje dijeljenje teksta koristeći servis pastebin.com -Comment[hu]=Szöveg megosztása a pastebin.com szolgáltatás használatával -Comment[ia]=Il permitte texto de esser compartite per usar le servicio pastebin.com -Comment[id]=Memungkinkan berbagi teks menggunakan layanan pastebin.com -Comment[is]=Gerir kleift að deila texta með pastebin.com límklippusafninu -Comment[it]=Abilita la condivisione di testo con il servizio pastebin.com -Comment[ja]=pastebin.com のサービスを用いたテキストの共有を有効にする -Comment[kk]=pastebin.com қызметі көмегімен мәтінді ортақ қылуды рұқсат ету -Comment[km]=បើក​ការ​ចែករំលែក​អត្ថបទ ដោយ​ប្រើ​សេវា pastebin.com -Comment[ko]=pastebin.com으로 텍스트를 공유합니다 -Comment[lt]=Įjungia dalinimąsį tekstu naudojant pastebin.com tarnybą -Comment[lv]=Ļauj kopīgot tekstu, izmantojot pastebin.com servisu -Comment[mr]=pastebin.com सेवा वापरुन पाठ्य शेअर करतो -Comment[nb]=Gjør det mulig å dele tekst via tjenesten pastebin.com -Comment[nds]=Text op "pastebin.com" praatstellen -Comment[nl]=Staat het delen van tekst toe met gebruik van de service pastebin.com -Comment[nn]=Gjer det mogleg å dela tekst via tenesta pastebin.com -Comment[pa]=pastebin.com ਸਰਵਿਸ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਟੈਕਸਟ ਸਾਂਝਾ ਕਰੋ -Comment[pl]=Udostępniania tekst przy użyciu usługi pastebin.com -Comment[pt]=Permite a partilha de texto com o serviço do 'pastebin.com' -Comment[pt_BR]=Permite o compartilhamento de texto usando o serviço do pastebin.com -Comment[ro]=Permite partajarea textului cu serviciul pastebin.com -Comment[ru]=Позволяет публиковать текст на сервере pastebin.com -Comment[sk]=Povolí zdieľanie textu pomocou služby pastebin.com -Comment[sl]=Omogoča deljenje besedila prek storitve pastebin.com -Comment[sr]=Дељење текста преко сервиса pastebin.com -Comment[sr@ijekavian]=Дијељење текста преко сервиса pastebin.com -Comment[sr@ijekavianlatin]=Dijeljenje teksta preko servisa pastebin.com -Comment[sr@latin]=Deljenje teksta preko servisa pastebin.com -Comment[sv]=Tillåter att text delas genom att använda tjänsten pastebin.com -Comment[tr]=pastebin.com servisini kullanarak metinleri paylaşmayı sağlar -Comment[ug]=تېكىستلەرنى pastebin.com مۇلازىمىتىنى ئىشلىتىپ ھەمبەھىرلەشكە ئىجازەت -Comment[uk]=Уможливлює оприлюднення фрагментів тексту за допомогою служби pastebin.com -Comment[vi]=Cho phép chia sẻ văn bản qua dịch vụ pastebin.com -Comment[wa]=Permete li pårtaedje di scrijhaedje e s' siervant do siervice pastebin.com -Comment[x-test]=xxAllows text to be shared using the pastebin.com servicexx -Comment[zh_CN]=用 pastebin.com 服务共享文本 -Comment[zh_TW]=允許使用 pastebin.com 服務分享文字 -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=text/* - -X-KDE-PluginInfo-Name=pastebincom -X-KDE-PluginInfo-Author=Artur de Souza -X-KDE-PluginInfo-Email=asouza@kde.org -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop - diff --git a/dataengines/share/backends/pasteopensuseorg/CMakeLists.txt b/dataengines/share/backends/pasteopensuseorg/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/pasteopensuseorg/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-pasteopensuseorg.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-pasteopensuseorg.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/pasteopensuseorg) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/pasteopensuseorg) diff --git a/dataengines/share/backends/pasteopensuseorg/metadata.desktop b/dataengines/share/backends/pasteopensuseorg/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/pasteopensuseorg/metadata.desktop +++ /dev/null @@ -1,129 +0,0 @@ -[Desktop Entry] -Name=paste.opensuse.org -Name[bg]=paste.opensuse.org -Name[bs]=paste.opensuse.org -Name[ca]=paste.opensuse.org -Name[ca@valencia]=paste.opensuse.org -Name[cs]=paste.opensuse.org -Name[da]=paste.opensuse.org -Name[de]=paste.opensuse.org -Name[el]=paste.opensuse.org -Name[en_GB]=paste.opensuse.org -Name[es]=paste.opensuse.org -Name[et]=paste.opensuse.org -Name[eu]=paste.opensuse.org -Name[fi]=paste.opensuse.org -Name[fr]=paste.opensuse.org -Name[ga]=paste.opensuse.org -Name[gl]=paste.opensuse.org -Name[gu]=paste.opensuse.org -Name[he]=paste.opensuse.org -Name[hi]=paste.opensuse.org -Name[hr]=paste.opensuse.org -Name[hu]=paste.opensuse.org -Name[ia]=paste.opensuse.org -Name[id]=paste.opensuse.org -Name[is]=paste.opensuse.org -Name[it]=paste.opensuse.org -Name[ja]=paste.opensuse.org -Name[kk]=paste.opensuse.org -Name[km]=paste.opensuse.org -Name[ko]=paste.opensuse.org -Name[lt]=paste.opensuse.org -Name[lv]=paste.opensuse.org -Name[mr]=paste.opensuse.org -Name[nb]=paste.opensuse.org -Name[nds]=paste.opensuse.org -Name[nl]=paste.opensuse.org -Name[nn]=paste.opensuse.org -Name[pa]=paste.opensuse.org -Name[pl]=paste.opensuse.org -Name[pt]=paste.opensuse.org -Name[pt_BR]=paste.opensuse.org -Name[ro]=paste.opensuse.org -Name[ru]=paste.opensuse.org -Name[sk]=paste.opensuse.org -Name[sl]=paste.opensuse.org -Name[sr]=paste.opensuse.org -Name[sr@ijekavian]=paste.opensuse.org -Name[sr@ijekavianlatin]=paste.opensuse.org -Name[sr@latin]=paste.opensuse.org -Name[sv]=paste.opensuse.org -Name[tg]=paste.opensuse.org -Name[th]=paste.opensuse.org -Name[tr]=paste.opensuse.org -Name[ug]=paste.opensuse.org -Name[uk]=paste.opensuse.org -Name[wa]=paste.opensuse.org -Name[x-test]=xxpaste.opensuse.orgxx -Name[zh_CN]=paste.opensuse.org -Name[zh_TW]=paste.opensuse.org -Comment=Paste text with openSUSE -Comment[ar]=الصق نصًّا بـأوبن سوزي -Comment[bg]=Поставяне на текст през openSUSE -Comment[bs]=Opensuseov servis za naljepljivanje teksta -Comment[ca]=Enganxa text amb openSUSE -Comment[ca@valencia]=Enganxa text amb openSUSE -Comment[cs]=Vložit text s využitím openSUSE -Comment[da]=Indsæt tekst med openSUSE -Comment[de]=Text auf paste.opensuse.org veröffentlichen -Comment[el]=Επικόλληση κειμένου με το openSUSE -Comment[en_GB]=Paste text with openSUSE -Comment[es]=Pegar texto con openSUSE -Comment[et]=Teksti asetamine openSUSE abil -Comment[eu]=Itsatsi testua OpenSUSE-rekin -Comment[fi]=Liitä tekstiä openSUSEn avulla -Comment[fr]=Colle du texte avec Open-SUSE -Comment[gl]=Pega texto con openSUSE -Comment[he]=שיתוף קטעי טקסט באמצעות openSUSE -Comment[hr]=Zalijepi tekst pomoću openSUSE-a -Comment[hu]=Szöveg beillesztése openSUSE-val -Comment[ia]=Colla texto con openSUSE -Comment[id]=Tempel teks menggunakan openSUSE -Comment[is]=Líma texta með openSUSE -Comment[it]=Incolla testo con openSUSE -Comment[ja]=openSUSE でテキストを貼りつける -Comment[kk]=openSUSE-ден мәтіді алып орналастыру -Comment[km]=បិទភ្ជាប់​​នៅ​ក្នុង​អូផឹនស៊ូស៊ី -Comment[ko]=openSUSE 텍스트 공유 서비스 -Comment[lt]=Padėti tekstą naudojant openSUSE -Comment[lv]=Ielīmēt tekstu ar openSUSE -Comment[mr]=openSUSE वापरुन पाठ्य चिटकवा -Comment[nb]=Lim inn tekst med openSUSE -Comment[nds]=Text op "openSUSE" praatstellen -Comment[nl]=Plak tekst met openSUSE -Comment[nn]=Lim inn tekst med openSUSE -Comment[pa]=openSUSE ਨਾਲ ਟੈਕਸਟ ਚੇਪੋopenSUSE ਨਾਲ ਟੈਕਸਟ ਚਪੇ -Comment[pl]=Wkleja tekst z openSUSE -Comment[pt]=Colar texto com o openSUSE -Comment[pt_BR]=Colar texto com o openSUSE -Comment[ro]=Lipește text cu openSUSE -Comment[ru]=Позволяет публиковать текст на сервере openSUSE -Comment[sk]=Vloží text pomocou služby openSUSE -Comment[sl]=Prilepite besedilo z openSUSE -Comment[sr]=Опенсусеов сервис за налепљивање текста -Comment[sr@ijekavian]=Опенсусеов сервис за наљепљивање текста -Comment[sr@ijekavianlatin]=OpenSuSE‑ov servis za naljepljivanje teksta -Comment[sr@latin]=OpenSuSE‑ov servis za nalepljivanje teksta -Comment[sv]=Klistra in text med openSUSE -Comment[tg]=Часпондани матн бо openSUSE -Comment[th]=แปะข้อความไปยังบริการของ openSUSE -Comment[tr]=Metinleri openSUSE servini kullanarak paylaş -Comment[ug]=openSUSE تىن تېكىست چاپلا -Comment[uk]=Вставити фрагмент тексту з openSUSE -Comment[wa]=Aclaper do tecse avou openSUSE -Comment[x-test]=xxPaste text with openSUSExx -Comment[zh_CN]=用 openSUSE 粘贴文本 -Comment[zh_TW]=用 openSUSE 貼上文字 -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=text/* - -X-KDE-PluginInfo-Name=pasteopensuseorg -X-KDE-PluginInfo-Author=Will Stephenson -X-KDE-PluginInfo-Email=wstephenson@suse.de -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop - diff --git a/dataengines/share/backends/pasteubuntucom/CMakeLists.txt b/dataengines/share/backends/pasteubuntucom/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/pasteubuntucom/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-pasteubuntucom.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-pasteubuntucom.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/pasteubuntucom) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/pasteubuntucom) diff --git a/dataengines/share/backends/pasteubuntucom/contents/code/main.js b/dataengines/share/backends/pasteubuntucom/contents/code/main.js deleted file mode 100644 --- a/dataengines/share/backends/pasteubuntucom/contents/code/main.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright © 2010 Harald Sitter - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -function url() { - return "http://paste.ubuntu.com"; -} - -function contentKey() { - return "content"; -} - -function setup() { - provider.addQueryItem("poster", "KDE"); - provider.addQueryItem("syntax", "text"); -} - -function handleResultData(data) { - // Whenever no redirection was received, it is an error - provider.error(data); -} - -function handleRedirection(url) { - provider.success(url); -} diff --git a/dataengines/share/backends/pasteubuntucom/metadata.desktop b/dataengines/share/backends/pasteubuntucom/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/pasteubuntucom/metadata.desktop +++ /dev/null @@ -1,70 +0,0 @@ -[Desktop Entry] -Name=paste.ubuntu.com -Name[bg]=paste.ubuntu.com -Name[bs]=paste.ubuntu.com -Name[ca]=paste.ubuntu.com -Name[ca@valencia]=paste.ubuntu.com -Name[cs]=paste.ubuntu.com -Name[da]=paste.ubuntu.com -Name[de]=paste.ubuntu.com -Name[el]=paste.ubuntu.com -Name[en_GB]=paste.ubuntu.com -Name[es]=paste.ubuntu.com -Name[et]=paste.ubuntu.com -Name[eu]=paste.ubuntu.com -Name[fi]=paste.ubuntu.com -Name[fr]=paste.ubuntu.com -Name[ga]=paste.ubuntu.com -Name[gl]=paste.ubuntu.com -Name[gu]=paste.ubuntu.com -Name[he]=paste.ubuntu.com -Name[hi]=paste.ubuntu.com -Name[hr]=paste.ubuntu.com -Name[hu]=paste.ubuntu.com -Name[ia]=paste.ubuntu.com -Name[id]=paste.ubuntu.com -Name[is]=paste.ubuntu.com -Name[it]=paste.ubuntu.com -Name[ja]=paste.ubuntu.com -Name[kk]=paste.ubuntu.com -Name[km]=paste.ubuntu.com -Name[ko]=paste.ubuntu.com -Name[lt]=paste.ubuntu.com -Name[lv]=paste.ubuntu.com -Name[mr]=paste.ubuntu.com -Name[nb]=paste.ubuntu.com -Name[nds]=paste.ubuntu.com -Name[nl]=paste.ubuntu.com -Name[nn]=paste.ubuntu.com -Name[pa]=paste.ubuntu.com -Name[pl]=paste.ubuntu.com -Name[pt]=paste.ubuntu.com -Name[pt_BR]=paste.ubuntu.com -Name[ro]=paste.ubuntu.com -Name[ru]=paste.ubuntu.com -Name[sk]=paste.ubuntu.com -Name[sl]=paste.ubuntu.com -Name[sr]=paste.ubuntu.com -Name[sr@ijekavian]=paste.ubuntu.com -Name[sr@ijekavianlatin]=paste.ubuntu.com -Name[sr@latin]=paste.ubuntu.com -Name[sv]=paste.ubuntu.com -Name[th]=paste.ubuntu.com -Name[tr]=paste.ubuntu.com -Name[ug]=paste.ubuntu.com -Name[uk]=paste.ubuntu.com -Name[wa]=paste.ubuntu.com -Name[x-test]=xxpaste.ubuntu.comxx -Name[zh_CN]=paste.ubuntu.com -Name[zh_TW]=paste.ubuntu.com -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=text/* - -X-KDE-PluginInfo-Name=pasteubuntucom -X-KDE-PluginInfo-Author=Harald Sitter -X-KDE-PluginInfo-Email=apachelogger@ubuntu.com -X-KDE-PluginInfo-Version=1.0 -X-KDE-PluginInfo-Website=http://www.ubuntu.com diff --git a/dataengines/share/backends/privatepaste/CMakeLists.txt b/dataengines/share/backends/privatepaste/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/privatepaste/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-privatepastecom.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-privatepastecom.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/privatepastecom) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/privatepastecom) diff --git a/dataengines/share/backends/privatepaste/metadata.desktop b/dataengines/share/backends/privatepaste/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/privatepaste/metadata.desktop +++ /dev/null @@ -1,127 +0,0 @@ -[Desktop Entry] -Name=privatepaste.com -Name[bg]=privatepaste.com -Name[bs]=privatepaste.com -Name[ca]=privatepaste.com -Name[ca@valencia]=privatepaste.com -Name[cs]=privatepaste.com -Name[da]=privatepaste.com -Name[de]=privatepaste.com -Name[el]=privatepaste.com -Name[en_GB]=privatepaste.com -Name[es]=privatepaste.com -Name[et]=privatepaste.com -Name[eu]=privatepaste.com -Name[fi]=privatepaste.com -Name[fr]=privatepaste.com -Name[ga]=privatepaste.com -Name[gl]=privatepaste.com -Name[gu]=privatepaste.com -Name[he]=privatepaste.com -Name[hi]=privatepaste.com -Name[hr]=privatepaste.com -Name[hu]=privatepaste.com -Name[ia]=privatepaste.com -Name[id]=privatepaste.com -Name[is]=privatepaste.com -Name[it]=privatepaste.com -Name[ja]=privatepaste.com -Name[kk]=privatepaste.com -Name[km]=privatepaste.com -Name[ko]=privatepaste.com -Name[lt]=privatepaste.com -Name[lv]=privatepaste.com -Name[mr]=privatepaste.com -Name[nb]=privatepaste.com -Name[nds]=privatepaste.com -Name[nl]=privatepaste.com -Name[nn]=privatepaste.com -Name[pa]=privatepaste.com -Name[pl]=privatepaste.com -Name[pt]=privatepaste.com -Name[pt_BR]=privatepaste.com -Name[ro]=privatepaste.com -Name[ru]=privatepaste.com -Name[sk]=privatepaste.com -Name[sl]=privatepaste.com -Name[sr]=privatepaste.com -Name[sr@ijekavian]=privatepaste.com -Name[sr@ijekavianlatin]=privatepaste.com -Name[sr@latin]=privatepaste.com -Name[sv]=privatepaste.com -Name[th]=privatepaste.com -Name[tr]=privatepaste.com -Name[ug]=privatepaste.com -Name[uk]=privatepaste.com -Name[wa]=privatepaste.com -Name[x-test]=xxprivatepaste.comxx -Name[zh_CN]=privatepaste.com -Name[zh_TW]=privatepaste.com -Comment=Paste text with the PrivatePaste.com service -Comment[ar]=الصق نصًّا بخدة PrivatePaste.com -Comment[bg]=Поставяне на текст през услугата PrivatePaste.com -Comment[bs]=Naljepljivanje teksta preko servisa privatepaste.com -Comment[ca]=Enganxa text amb el servei PrivatePaste.com -Comment[ca@valencia]=Enganxa text amb el servei PrivatePaste.com -Comment[cs]=Vložit text s využitím služby PrivatePaste.com -Comment[da]=Indsæt tekst med tjenesten PrivatePaste.com -Comment[de]=Text auf PrivatePaste.com veröffentlichen -Comment[el]=Επικόλληση κειμένου με την υπηρεσία PrivatePaste.com -Comment[en_GB]=Paste text with the PrivatePaste.com service -Comment[es]=Pegar texto mediante el servicio PrivatePaste.com -Comment[et]=Teksti asetamine PrivatePaste.com teenuse abil -Comment[eu]=Itsatsi testua PrivatePaste.com zerbitzuaren bidez -Comment[fi]=Liitä tekstiä PrivatePaste.com-palvelun avulla -Comment[fr]=Colle du texte à l'aide du service « PrivatePaste.com » -Comment[gl]=Pega texto co servizo PrivatePaste.com -Comment[he]=שיתוף קטעי טקסט באמצעות PrivatePaste.com -Comment[hr]=Zalijepi tekst pomoću servisa PrivatePaste.com -Comment[hu]=Szöveg beillesztése a PrivatePaste.com szolgáltatással -Comment[ia]=Colla texto con le servicio PrivatePaste.com -Comment[id]=Tempel teks dengan layanan PrivatePaste.com -Comment[is]=Líma texta með PrivatePaste.com límklippusafninu -Comment[it]=Incolla testo con il servizio PrivatePaste.com -Comment[ja]=PrivatePaste.com のサービスを用いてテキストを貼り付ける -Comment[kk]=PrivatePaste.com қызметінен мәтінді алып орналастыру -Comment[km]=បិទភ្ជាប់​អត្ថបទ​ដោយ​ប្រើ​សេវា PrivatePaste.com -Comment[ko]=PrivatePaste.com으로 텍스트를 공유합니다 -Comment[lt]=Padėti tekstą naudojant PrivatePaste.com tarnybą -Comment[lv]=Ielīmēt tekstu ar PrivatePaste.com servisu -Comment[mr]=privatepaste.com सेवा वापरुन पाठ्य चिटकवा -Comment[nb]=Lim inn tekst med tjenesten PrivatePaste.com -Comment[nds]=Text op "PrivatePaste.com" praatstellen -Comment[nl]=Plak tekst met de service PrivatePaste.com -Comment[nn]=Lim inn tekst med tenesta PrivatePaste.com -Comment[pa]=PrivatePaste.com ਸਰਵਿਸ ਨਾਲ ਟੈਕਸਟ ਚੇਪੋ -Comment[pl]=Wkleja tekst z usługą PrivatePaste.com -Comment[pt]=Colar texto com o serviço PrivatePaste.com -Comment[pt_BR]=Colar texto com o serviço PrivatePaste.com -Comment[ro]=Lipește text cu serviciul PrivatePaste.com -Comment[ru]=Позволяет публиковать текст на сервере PrivatePaste.com -Comment[sk]=Vloží text pomocou služby PrivatePaste.com -Comment[sl]=Prilepite besedilo s storitvijo PrivatePaste.com -Comment[sr]=Налепљивање текста преко сервиса privatepaste.com -Comment[sr@ijekavian]=Наљепљивање текста преко сервиса privatepaste.com -Comment[sr@ijekavianlatin]=Naljepljivanje teksta preko servisa privatepaste.com -Comment[sr@latin]=Nalepljivanje teksta preko servisa privatepaste.com -Comment[sv]=Klistra in text med tjänsten PrivatePaste.com -Comment[th]=แปะข้อความไปยังบริการของ PrivatePaste.com -Comment[tr]=Metinleri PrivatePaste.com servisini kullanarak paylaş -Comment[ug]=PrivatePaste.com مۇلازىمىتىدىن تېكىست چاپلا -Comment[uk]=Вставка фрагментів тексту за допомогою служби PrivatePaste.com -Comment[wa]=Aclaper do tecse avou l' siervice PrivatePaste.com -Comment[x-test]=xxPaste text with the PrivatePaste.com servicexx -Comment[zh_CN]=用 PrivatePaste.com 服务粘贴文本 -Comment[zh_TW]=用 PrivatePaste.com 服務貼上文字 -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=text/* - -X-KDE-PluginInfo-Name=privatepastecom -X-KDE-PluginInfo-Author=Artur Souza -X-KDE-PluginInfo-Email=asouza@kde.org -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop - diff --git a/dataengines/share/backends/simplestimagehosting/CMakeLists.txt b/dataengines/share/backends/simplestimagehosting/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/simplestimagehosting/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-simplestimagehosting.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-simplestimagehosting.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/simplestimagehosting/) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/simplestimagehosting) diff --git a/dataengines/share/backends/simplestimagehosting/metadata.desktop b/dataengines/share/backends/simplestimagehosting/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/simplestimagehosting/metadata.desktop +++ /dev/null @@ -1,125 +0,0 @@ -[Desktop Entry] -Name=Simplest Image Hosting -Name[ar]=أسهل استضافة للصور -Name[bg]=Simplest Image Hosting -Name[bs]=Simplest Image Hosting -Name[ca]=Simplest Image Hosting -Name[ca@valencia]=Simplest Image Hosting -Name[cs]=Simplest Image Hosting -Name[da]=Simplest Image Hosting -Name[de]=Simplest Image Hosting -Name[el]=Simplest Image Hosting -Name[en_GB]=Simplest Image Hosting -Name[es]=Simplest Image Hosting -Name[et]=Simplest Image Hosting -Name[eu]=Simplest Image Hosting -Name[fi]=Simplest Image Hosting -Name[fr]=Simplest Image Hosting -Name[gl]=Simplest Image Hosting -Name[he]=Simplest Image Hosting -Name[hr]=Simplest Image Hosting -Name[hu]=Simplest Image Hosting -Name[ia]=Simplest Image Hosting -Name[id]=Simplest Image Hosting -Name[is]=Simplest Image Hosting -Name[it]=Simplest Image Hosting -Name[ja]=Simplest Image Hosting -Name[kk]=Simplest Image Hosting -Name[km]=ការ​បង្ហោះរូបភាព​សាមញ្ញ​បំផុត -Name[ko]=Simplest Image Hosting -Name[lt]=Paprasčiausias nuotraukų talpinimas -Name[lv]=Simplest Image Hosting -Name[mr]=सिंप्लेस्ट प्रतिमा होस्टींग -Name[nb]=Simplest Image Hosting -Name[nds]=Simplest-Image-Hosting -Name[nl]=Simplest Image Hosting -Name[nn]=Simplest Image Hosting -Name[pa]=ਸੈਂਪਲਸਟ ਈਮੇਜ਼ ਹੋਸਟਿੰਗ -Name[pl]=Simplest Image Hosting -Name[pt]=Simplest Image Hosting -Name[pt_BR]=Simplest Image Hosting -Name[ro]=Cea mai simplă găzduire de imagini -Name[ru]=Simplest-Image-Hosting.net -Name[sk]=Simplest Image Hosting -Name[sl]=Simplest Image Hosting -Name[sr]=Симплест имејџ хостинг -Name[sr@ijekavian]=Симплест имејџ хостинг -Name[sr@ijekavianlatin]=Simplest Image Hosting -Name[sr@latin]=Simplest Image Hosting -Name[sv]=Simplest Image Hosting -Name[th]=บริการ Simplest Image Hosting -Name[tr]=Simplest Image Hosting -Name[ug]=Simplest Image Hosting -Name[uk]=Simplest Image Hosting -Name[wa]=Simplest Image Hosting -Name[x-test]=xxSimplest Image Hostingxx -Name[zh_CN]=Simplest Image Hosting -Name[zh_TW]=Simplest Image Hosting -Comment=Allows images to be shared using the Simplest Image Hosting service -Comment[ar]=يسمح بمشاركة الصور عبر خدمة أسهل استضافة للصور -Comment[bg]=Позволява споделяне на изображения чрез услугата Simplest Image Hosting -Comment[bs]=Dijeljenje slika preko servisa Simplest image hosting -Comment[ca]=Permet la compartició d'imatges utilitzant el servei «Simplest Image Hosting» -Comment[ca@valencia]=Permet la compartició d'imatges utilitzant el servei «Simplest Image Hosting» -Comment[cs]=Povolí sdílení obrázků pomocí služby Simplest Image Hosting -Comment[da]=Muliggør deling af billeder med tjenesten Simplest Image Hosting -Comment[de]=Ermöglicht die Veröffentlichung von Bildern über den simplest-image-hosting.net-Dienst -Comment[el]=Επιτρέπει την κοινή χρήση των εικόνων χρησιμοποιώντας την υπηρεσία Simplest Image Hosting -Comment[en_GB]=Allows images to be shared using the Simplest Image Hosting service -Comment[es]=Permite compartir imágenes a través del servicio de Simplest Image Hosting -Comment[et]=Piltide jagamine Simplest Image Hosting teenuse kaudu -Comment[eu]=Irudiak Simplest Image Hosting zerbitzuaren bidez partekatzeko aukera ematen du -Comment[fi]=Mahdollistaa kuvien jakamisen Simplest Image Hosting -palvelussa -Comment[fr]=Permet le partage d'images à l'aide du service « Simplest Image Hosting » -Comment[gl]=Permite compartir imaxes mediante o servizo Simplest Image Hosting -Comment[he]=מאפשר שיתוף תמונות באמצעות השירות Simplest Image Hosting -Comment[hr]=Omogućuje dijeljenje slika koristeći servis Simplest Image Hosting -Comment[hu]=Képek megosztása a Simple Image Hosting szolgáltatás használatával -Comment[ia]=Il permitte imagines de esser compartite per usar le servicio Simplest Image Hosting -Comment[id]=Memungkinkan berbagi gambar menggunakan layanan Simplest Image Hosting -Comment[is]=Gerir kleift að deila myndum með Simplest Image Hosting þjónustunni -Comment[it]=Abilita la condivisione di immagini con il servizio Simplest Image Hosting -Comment[ja]=Simplest Image Hosting のサービスを用いたテキストの共有を有効にする -Comment[kk]=Simplest Image Hosting қызметі көмегімен кескіндерді ортақ қылуды рұқсат ету -Comment[km]=បើក​ការ​ចែករំលែក​រូបភាព​ ដោយ​ប្រើ​សេវា​បង្ហោះ​រូបភាព​សាមញ្ញ​បំផុត​ -Comment[ko]=Simplest Image Hosting으로 그림을 공유합니다 -Comment[lt]=Įjungia dalinimąsį nuotraukomis naudojant paprasčiausio nuotraukų talpinimo tarnybą -Comment[lv]=Ļauj kopīgot attēlus, izmantojot Simplest Image Hosting servisu -Comment[mr]=सिंप्लेस्ट प्रतिमा होस्टींग सेवा वापरुन प्रतिमा शेअर करतो -Comment[nb]=Gjør det mulig å dele bilder via tjenesten Simplest Image Hosting -Comment[nds]=Biller op "Simplest Image Hosting" praatstellen -Comment[nl]=Staat het delen van afbeeldingen toe met gebruik van de service Simplest Image Hosting -Comment[nn]=Gjer det mogleg å dela bilete via tenesta Simplest Image Hosting -Comment[pa]=ਸੈਂਪਲਸਟ ਈਮੇਜ਼ ਹੋਸਟਿੰਗ ਸਰਵਿਸ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਚਿੱਤਰ ਸਾਂਝੇ ਕਰਨ ਲਈ ਸਹਾਇਕ -Comment[pl]=Udostępniania obrazy przy użyciu usługi Simplest Image Hosting -Comment[pt]=Permite a partilha de imagens com o serviço do Simplest Image Hosting -Comment[pt_BR]=Permite o compartilhamento de imagens usando o serviço do Simplest Image Hosting -Comment[ro]=Permite ca imaginile să fie partajate folosind serviciul Simplest Image Hosting -Comment[ru]=Позволяет публиковать изображения на сервере Simplest Image Hosting -Comment[sk]=Povolí zdieľanie obrázkov pomocou služby Simplest Image Hosting -Comment[sl]=Omogoča deljenje slik prek storitve Simplest Image Hosting -Comment[sr]=Дељење слика преко сервиса Симплест имејџ хостинг -Comment[sr@ijekavian]=Дијељење слика преко сервиса Симплест имејџ хостинг -Comment[sr@ijekavianlatin]=Dijeljenje slika preko servisa Simplest Image Hosting -Comment[sr@latin]=Deljenje slika preko servisa Simplest Image Hosting -Comment[sv]=Tillåter att bilder delas genom att använda Simplest Image Hosting -Comment[tr]=Simplest Image Hosting servisini kullanarak resimleri paylaşmayı sağlar -Comment[ug]=رەسىملەرنى Simplest Image Hosting مۇلازىمىتىنى ئىشلىتىپ ھەمبەھىرلەشكە ئىجازەت -Comment[uk]=Уможливлює оприлюднення зображень за допомогою служби Simplest Image Hosting -Comment[vi]=Cho phép chia sẻ ảnh qua dịch vụ Simplet Image Hosting -Comment[wa]=Permete li pårtaedje d' imådjes e s' siervant do siervice Simplest Image Hosting -Comment[x-test]=xxAllows images to be shared using the Simplest Image Hosting servicexx -Comment[zh_CN]=用 Simplest Image Hosting 服务共享图片 -Comment[zh_TW]=允許使用 Simplest Image Hosting 服務分享影像 -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=image/* - -X-KDE-PluginInfo-Name=simplestimagehosting -X-KDE-PluginInfo-Author=Artur de Souza -X-KDE-PluginInfo-Email=asouza@kde.org -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop - diff --git a/dataengines/share/backends/wklej/CMakeLists.txt b/dataengines/share/backends/wklej/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/wklej/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-wklej.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-wklej.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/wklej/) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/wklej) diff --git a/dataengines/share/backends/wklej/contents/code/main.js b/dataengines/share/backends/wklej/contents/code/main.js deleted file mode 100644 --- a/dataengines/share/backends/wklej/contents/code/main.js +++ /dev/null @@ -1,41 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2010 by Artur Duque de Souza * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * - ***************************************************************************/ - -function url() { - return "http://wklej.org"; -} - -function contentKey() { - return "body"; -} - -function setup() { - provider.addQueryItem("autor", "kde"); - provider.addQueryItem("syntax", "text"); -} - -function handleResultData(data) { - var res = provider.parseXML("title", data); - if (res == "") { - provider.error(data); - } - var id = res.split(" ")[1].replace("#", "http://wklej.org/id/"); - provider.success(id); -} - diff --git a/dataengines/share/backends/wklej/metadata.desktop b/dataengines/share/backends/wklej/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/wklej/metadata.desktop +++ /dev/null @@ -1,128 +0,0 @@ -[Desktop Entry] -Name=wklej.org -Name[bg]=wklej.org -Name[bs]=wklej.org -Name[ca]=wklej.org -Name[ca@valencia]=wklej.org -Name[cs]=wklej.org -Name[da]=wklej.org -Name[de]=wklej.org -Name[el]=wklej.org -Name[en_GB]=wklej.org -Name[es]=wklej.org -Name[et]=wklej.org -Name[eu]=wklej.org -Name[fi]=wklej.org -Name[fr]=wklej.org -Name[ga]=wklej.org -Name[gl]=wklej.org -Name[gu]=wklej.org -Name[he]=wklej.org -Name[hi]=wklej.org -Name[hr]=wklej.org -Name[hu]=wklej.org -Name[ia]=wklej.org -Name[id]=wklej.org -Name[is]=wklej.org -Name[it]=wklej.org -Name[ja]=wklej.org -Name[kk]=wklej.org -Name[km]=wklej.org -Name[ko]=wklej.org -Name[lt]=wklej.org -Name[lv]=wklej.org -Name[mr]=wklej.org -Name[nb]=wklej.org -Name[nds]=wklej.org -Name[nl]=wklej.org -Name[nn]=wklej.org -Name[pa]=wklej.org -Name[pl]=wklej.org -Name[pt]=wklej.org -Name[pt_BR]=wklej.org -Name[ro]=wklej.org -Name[ru]=wklej.org -Name[sk]=wklej.org -Name[sl]=wklej.org -Name[sr]=wklej.org -Name[sr@ijekavian]=wklej.org -Name[sr@ijekavianlatin]=wklej.org -Name[sr@latin]=wklej.org -Name[sv]=wklej.org -Name[tg]=wklej.org -Name[th]=wklej.org -Name[tr]=wklej.org -Name[ug]=wklej.org -Name[uk]=wklej.org -Name[wa]=wklej.org -Name[x-test]=xxwklej.orgxx -Name[zh_CN]=wklej.org -Name[zh_TW]=wklej.org -Comment=Allows text to be shared using the wklej.org service -Comment[ar]=يسمح بمشاركة النصوص عبر خدمة wklej.org -Comment[bg]=Позволява споделянето на текст чрез wklej.org -Comment[bs]=Dopušta dijeljenje teksta preko servisa wklej.org -Comment[ca]=Permet la compartició de text utilitzant el servei wklej.org -Comment[ca@valencia]=Permet la compartició de text utilitzant el servei wklej.org -Comment[cs]=Povolí sdílení textů pomocí služby wklej.org -Comment[da]=Muliggør deling af tekst med tjenesten wklej.org -Comment[de]=Ermöglicht die Veröffentlichung von Text über den wklej.org-Dienst -Comment[el]=Επιτρέπει στο κείμενο να γίνει κοινόχρηστο χρησιμοποιώντας την υπηρεσία wklej.org -Comment[en_GB]=Allows text to be shared using the wklej.org service -Comment[es]=Permite compartir texto a través del servicio de wklej.org -Comment[et]=Teksti jagamine wklej.org teenuse kaudu -Comment[eu]=Testua wklej.org zerbitzuaren bidez partekatzeko aukera ematen du -Comment[fi]=Mahdollistaa tekstin jakamisen wklej.org-palvelussa -Comment[fr]=Permet le partage de texte à l'aide du service « wklej.org » -Comment[gl]=Permite compartir texto mediante o servizo wklej.org -Comment[he]=מאפשר שיתוף טקסט באמצעות השירות wklej.org -Comment[hr]=Omogućuje dijeljenje teksta koristeći servis wklej.org -Comment[hu]=Szöveg megosztása a wklej.org szolgáltatás használatával -Comment[ia]=Il permitte texto de esser compartite per usar le servicio wklej.org -Comment[id]=Memungkinkan berbagi teks menggunakan layanan wklej.org -Comment[is]=Gerir kleift að deila texta með wklej.org þjónustunni -Comment[it]=Abilita la condivisione di testo con il servizio wklej.org -Comment[ja]=wklej.org のサービスを用いたテキストの共有を有効にする -Comment[kk]=wklej.org қызметі көмегімен мәтінді ортақ қылуды рұқсат ету -Comment[km]=បើក​ការ​ចែករំលែក​អត្ថបទ​ដោយ​ប្រើ​សេវា wklej.org -Comment[ko]=wklej.org로 텍스트를 공유합니다 -Comment[lt]=Įjungia dalinimąsį tekstu naudojant wklej.org tarnybą -Comment[lv]=Ļauj kopīgot tekstu, izmantojot wklej.org servisu -Comment[mr]=wklej.org सेवा वापरुन पाठ्य शेअर करतो -Comment[nb]=Gjør det mulig å dele tekst via tjenesten wklej.org -Comment[nds]=Text op "wklej.org" praatstellen -Comment[nl]=Staat het delen van tekst toe met gebruik van de service wklej.org -Comment[nn]=Gjer det mogleg å dela tekst via tenesta wklej.org -Comment[pa]=wklej.org ਸਰਵਿਸ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਟੈਕਸਟ ਸਾਂਝਾ ਕਰਨਾ ਯੋਗ -Comment[pl]=Udostępniania tekst przy użyciu usługi wklej.org -Comment[pt]=Permite a partilha de texto com o serviço do 'wklej.org' -Comment[pt_BR]=Permite o compartilhamento de texto com o serviço do wklej.org -Comment[ro]=Permite partajarea textului cu serviciul wklej.org -Comment[ru]=Позволяет публиковать текст на сервере wklej.org -Comment[sk]=Povolí zdieľanie textu pomocou služby wklej.org -Comment[sl]=Omogoča deljenje besedila prek storitve wklej.org -Comment[sr]=Дељење текста преко сервиса wklej.org -Comment[sr@ijekavian]=Дијељење текста преко сервиса wklej.org -Comment[sr@ijekavianlatin]=Dijeljenje teksta preko servisa wklej.org -Comment[sr@latin]=Deljenje teksta preko servisa wklej.org -Comment[sv]=Tillåter att text delas genom att använda tjänsten wklej.org -Comment[tr]=wklej.org servisini kullanarak metinleri paylaşmayı sağlar -Comment[ug]=تېكىستلەرنى wklej.org مۇلازىمىتىنى ئىشلىتىپ ھەمبەھىرلەشكە ئىجازەت -Comment[uk]=Уможливлює оприлюднення фрагментів тексту за допомогою служби wklej.org -Comment[vi]=Cho phép chia sẻ văn bản qua dịch vụ wklej.org -Comment[wa]=Permete li pårtaedje di scrijhaedje e s' siervant do siervice wklej.org -Comment[x-test]=xxAllows text to be shared using the wklej.org servicexx -Comment[zh_CN]=用 wklej.org服务共享文字 -Comment[zh_TW]=允許使用 wklej.org 服務分享文字 -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=text/* - -X-KDE-PluginInfo-Name=wklej -X-KDE-PluginInfo-Author=Artur de Souza -X-KDE-PluginInfo-Email=asouza@kde.org -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop - diff --git a/dataengines/share/backends/wstaw/CMakeLists.txt b/dataengines/share/backends/wstaw/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/backends/wstaw/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.desktop ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-wstaw.desktop COPYONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-dataengine-share-addon-wstaw.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(FILES metadata.desktop - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/wstaw/) - -install(DIRECTORY contents - DESTINATION ${PLASMA_DATA_INSTALL_DIR}/shareprovider/wstaw) diff --git a/dataengines/share/backends/wstaw/metadata.desktop b/dataengines/share/backends/wstaw/metadata.desktop deleted file mode 100644 --- a/dataengines/share/backends/wstaw/metadata.desktop +++ /dev/null @@ -1,129 +0,0 @@ -[Desktop Entry] -Name=wstaw.org -Name[bg]=wstaw.org -Name[bs]=wstaw.org -Name[ca]=wstaw.org -Name[ca@valencia]=wstaw.org -Name[cs]=wstaw.org -Name[da]=wstaw.org -Name[de]=wstaw.org -Name[el]=wstaw.org -Name[en_GB]=wstaw.org -Name[es]=wstaw.org -Name[et]=wstaw.org -Name[eu]=wstaw.org -Name[fi]=wstaw.org -Name[fr]=wstaw.org -Name[ga]=wstaw.org -Name[gl]=wstaw.org -Name[gu]=wstaw.org -Name[he]=wstaw.org -Name[hi]=wstaw.org -Name[hr]=wstaw.org -Name[hu]=wstaw.org -Name[ia]=wstaw.org -Name[id]=wstaw.org -Name[is]=wstaw.org -Name[it]=wstaw.org -Name[ja]=wstaw.org -Name[kk]=wstaw.org -Name[km]=wstaw.org -Name[ko]=wstaw.org -Name[lt]=wstaw.org -Name[lv]=wstaw.org -Name[mai]=wstaw.org -Name[mr]=wstaw.org -Name[nb]=wstaw.org -Name[nds]=wstaw.org -Name[nl]=wstaw.org -Name[nn]=wstaw.org -Name[pa]=wstaw.org -Name[pl]=wstaw.org -Name[pt]=wstaw.org -Name[pt_BR]=wstaw.org -Name[ro]=wstaw.org -Name[ru]=wstaw.org -Name[sk]=wstaw.org -Name[sl]=wstaw.org -Name[sr]=wstaw.org -Name[sr@ijekavian]=wstaw.org -Name[sr@ijekavianlatin]=wstaw.org -Name[sr@latin]=wstaw.org -Name[sv]=wstaw.org -Name[tg]=wstaw.org -Name[th]=wstaw.org -Name[tr]=wstaw.org -Name[ug]=wstaw.org -Name[uk]=wstaw.org -Name[wa]=wstaw.org -Name[x-test]=xxwstaw.orgxx -Name[zh_CN]=wstaw.org -Name[zh_TW]=wstaw.org -Comment=Allows images to be shared using the wstaw.org service -Comment[ar]=يسمح بمشاركة الصور عبر خدمة wstaw.org -Comment[bg]=Позволява споделянето на изображения чрез wstaw.org -Comment[bs]=Dopušta dijeljenje slika preko servisa wstaw.org -Comment[ca]=Permet la compartició d'imatges utilitzant el servei wstaw.org -Comment[ca@valencia]=Permet la compartició d'imatges utilitzant el servei wstaw.org -Comment[cs]=Povolí sdílení obrázků pomocí služby wstaw.org -Comment[da]=Muliggør deling af billeder med tjenesten wstaw.org -Comment[de]=Ermöglicht die Veröffentlichung von Bildern über den wstaw.org-Dienst -Comment[el]=Επιτρέπει το μοίρασμα των εικόνων χρησιμοποιώντας την υπηρεσία wstaw.org -Comment[en_GB]=Allows images to be shared using the wstaw.org service -Comment[es]=Permite compartir imágenes a través del servicio de wstaw.org -Comment[et]=Piltide jagamine wstaw.org teenuse kaudu -Comment[eu]=Irudiak wstaw.org zerbitzuaren bidez partekatzeko aukera ematen du -Comment[fi]=Mahdollistaa kuvien jakamisen wstaw.org-palvelussa -Comment[fr]=Permet le partage d'images à l'aide du service « wstaw.org » -Comment[gl]=Permite compartir imaxes mediante o servizo wstaw.org -Comment[he]=מאפשר שיתוף תמונות באמצעות השירות wstaw.org -Comment[hr]=Omogućuje dijeljenje slika koristeći servis wstaw.org -Comment[hu]=Képek megosztása a wstaw.org szolgáltatás használatával -Comment[ia]=Il permitte images de esser compartite per usar le servicio wstaw.org -Comment[id]=Memungkinkan berbagi gambar menggunakan layanan wstaw.org -Comment[is]=Gerir kleift að deila myndum með wstaw.org þjónustunni -Comment[it]=Abilita la condivisione di immagini con il servizio wstaw.org -Comment[ja]=wstaw.org のサービスを用いた画像の共有を有効にする -Comment[kk]=wstaw.org қызметі көмегімен кескіндерді ортақ қылуды рұқсат ету -Comment[km]=បើក​ការ​ចែករំលែក​រូបភាព​ ដោយ​ប្រើ​សេវា wstaw.org -Comment[ko]=wstaw.org로 그림을 공유합니다 -Comment[lt]=Įjungia dalinimąsį nuotraukomis naudojant wstaw.org tarnybą -Comment[lv]=Ļauj kopīgot attēlus, izmantojot wstaw.org servisu -Comment[mr]=wstaw.org सेवा वापरुन प्रतिमा शेअर करतो -Comment[nb]=Gjør det mulig å dele bilder via tjenesten wstaw.org -Comment[nds]=Biller op "wstaw.org" praatstellen -Comment[nl]=Staat het delen van afbeeldingen toe met gebruik van de service wstaw.org -Comment[nn]=Gjer det mogleg å dela bilete via tenesta wstaw.org -Comment[pa]=wstaw.org ਸਰਵਿਸ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਚਿੱਤਰ ਸਾਂਝੇ ਕਰਨ ਲਈ ਸਹਾਇਕ -Comment[pl]=Udostępniania obrazy przy użyciu usługi wstaw.org -Comment[pt]=Permite a partilha de imagens com o serviço do 'wstaw.org' -Comment[pt_BR]=Permite o compartilhamento de imagens usando o serviço do wstaw.org -Comment[ro]=Permite partajarea imaginilor cu serviciul wstaw.org -Comment[ru]=Позволяет публиковать изображения на сервере wstaw.org -Comment[sk]=Povolí zdieľanie obrázkov pomocou služby wstaw.org -Comment[sl]=Omogoča deljenje slik prek storitve wstaw.org -Comment[sr]=Дељење слика преко сервиса wstaw.org -Comment[sr@ijekavian]=Дијељење слика преко сервиса wstaw.org -Comment[sr@ijekavianlatin]=Dijeljenje slika preko servisa wstaw.org -Comment[sr@latin]=Deljenje slika preko servisa wstaw.org -Comment[sv]=Tillåter att bilder delas genom att använda tjänsten wstaw.org -Comment[tr]=wstaw.org servisi kullanarak resimleri paylaşmayı sağlar -Comment[ug]=رەسىملەرنى wstaw.org مۇلازىمىتىنى ئىشلىتىپ ھەمبەھىرلەشكە ئىجازەت -Comment[uk]=Уможливлює оприлюднення зображень за допомогою служби wstaw.org -Comment[vi]=Cho phép chia sẻ ảnh qua dịch vụ wstaw.org -Comment[wa]=Permete li pårtaedje d' imådjes e s' siervant do siervice wstaw.org -Comment[x-test]=xxAllows images to be shared using the wstaw.org servicexx -Comment[zh_CN]=用 wstaw.org 服务共享图片 -Comment[zh_TW]=允許使用 wstaw.org 服務分享影像 -Type=Service - -X-KDE-ServiceTypes=Plasma/ShareProvider -X-KDE-Library=plasma_engine_share -X-KDE-PlasmaShareProvider-MimeType=image/* - -X-KDE-PluginInfo-Name=wstaw -X-KDE-PluginInfo-Author=Artur de Souza -X-KDE-PluginInfo-Email=asouza@kde.org -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop - diff --git a/dataengines/share/data/plasma_shareprovider.desktop b/dataengines/share/data/plasma_shareprovider.desktop deleted file mode 100644 --- a/dataengines/share/data/plasma_shareprovider.desktop +++ /dev/null @@ -1,62 +0,0 @@ -[Desktop Entry] -Type=ServiceType -X-KDE-ServiceType=Plasma/ShareProvider -Comment=Plugin for Plasma Sharebin -Comment[ar]=ملحقة لبلازما Sharebin -Comment[bs]=Priključak za plazma korpu dijeljenja -Comment[ca]=Connector pel Sharebin del Plasma -Comment[ca@valencia]=Connector pel Sharebin del Plasma -Comment[cs]=Zásuvný modul pro Plasma Sharebin -Comment[da]=Plugin til Plasma Sharebin -Comment[de]=Modul für Plasma-Sharebin -Comment[el]=Πρόσθετο για το Plasma Sharebin -Comment[en_GB]=Plugin for Plasma Sharebin -Comment[es]=Complemento para Plasma Sharebin -Comment[et]=Plasma Sharebini plugin -Comment[eu]=Plasmaren Sharebin-erako plugina -Comment[fi]=Plasman Sharebin-liitännäinen -Comment[fr]=Module externe « Sharebin » pour Plasma -Comment[gl]=Complemento para o motor Sharebin de Plasma -Comment[he]=תוסף עבור Plasma Sharebin -Comment[hr]=Priključak za Plasma Sharebin -Comment[hu]=Bővítmény a Plasma Sharebinhez -Comment[ia]=Plugin pro Plasma Sharebin -Comment[id]=Plugin Plasma Sharebin -Comment[is]=Íforrit fyrir Plasma Sharebin -Comment[it]=Estensione per Sharebin di Plasma -Comment[ja]=Plasma Sharebin プラグイン -Comment[kk]=Pasma ортақ дерегінің плагині -Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់​ Sharebin ប្លាស្មា -Comment[ko]=Plasma Sharebin 설정 -Comment[lt]=Plasma dalinimosi dėžės papildinys -Comment[lv]=Spraudnis priekš Plasma Sharebin -Comment[mr]=प्लाज्मा शेअरबिन करिता प्लगइन -Comment[nb]=Programtillegg for Plasma Sharebin -Comment[nds]=Moduul för "Plasma-Sharebin" -Comment[nl]=Plugin voor Plasma Sharebin -Comment[nn]=Programtillegg for Plasma Sharebin -Comment[pa]=ਪਲਾਜ਼ਮਾ ਸ਼ੇਅਰਬਿਨ ਲਈ ਪਲੱਗਇਨ -Comment[pl]=Wtyczka dla Sharebin plazmy -Comment[pt]='Plugin' para o Sharebin do Plasma -Comment[pt_BR]=Plugin para o Sharebin do Plasma -Comment[ro]=Modul pentru Plasma Sharebin -Comment[ru]=Расширение для Plasma Sharebin -Comment[sk]=Modul pre Plasma Sharebin -Comment[sl]=Vstavek za Plasma Sharebin -Comment[sr]=Прикључак за плазма корпу дељења -Comment[sr@ijekavian]=Прикључак за плазма корпу дијељења -Comment[sr@ijekavianlatin]=Priključak za plasma korpu dijeljenja -Comment[sr@latin]=Priključak za plasma korpu deljenja -Comment[sv]=Insticksprogram för Plasma Sharebin -Comment[th]=ส่วนเสริมสำหรับถังขยะร่วมกันของพลาสมา -Comment[tr]=Plasma PaylaşımKutusu için Eklenti -Comment[ug]=پلازما Sharebin ئۈچۈن قىستۇرما -Comment[uk]=Додаток до спільного ресурсу Плазми -Comment[wa]=Tchôke-divins pol batch di pårtaedje di Plasma -Comment[x-test]=xxPlugin for Plasma Sharebinxx -Comment[zh_CN]=Plasma 共享插件 -Comment[zh_TW]=Plasma Sharebin 的外掛程式 - -[PropertyDef::X-KDE-PlasmaShareProvider-MimeType] -Type=QStringList - diff --git a/dataengines/share/packagestructure/CMakeLists.txt b/dataengines/share/packagestructure/CMakeLists.txt deleted file mode 100644 --- a/dataengines/share/packagestructure/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -set(sharepackage_SRCS - share_package.cpp -) - -add_library(plasma_packagestructure_share MODULE ${sharepackage_SRCS}) -kcoreaddons_desktop_to_json(plasma_packagestructure_share plasma-packagestructure-share.desktop) -target_link_libraries(plasma_packagestructure_share - KF5::Package - KF5::I18n - KF5::Service - Qt5::Gui -) - -install(TARGETS plasma_packagestructure_share - DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/packagestructure) -#install(FILES data/plasma-packagestructure-share.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) - - diff --git a/dataengines/share/packagestructure/plasma-packagestructure-share.desktop b/dataengines/share/packagestructure/plasma-packagestructure-share.desktop deleted file mode 100644 --- a/dataengines/share/packagestructure/plasma-packagestructure-share.desktop +++ /dev/null @@ -1,124 +0,0 @@ -[Desktop Entry] -Name=ShareProvider -Name[bg]=Доставчик на услуги за споделяне -Name[bs]=Dobavljač dijeljenja -Name[ca]=ShareProvider -Name[ca@valencia]=ShareProvider -Name[cs]=ShareProvider -Name[da]=Delingsudbyder -Name[de]=Veröffentlichungsanbieter -Name[el]=Πάροχος κοινής χρήσης -Name[en_GB]=ShareProvider -Name[es]=Compartir proveedor -Name[et]=ShareProvider -Name[eu]=Partekatze-hornitzailea -Name[fi]=ShareProvider -Name[fr]=Fournisseur de partages -Name[gl]=ShareProvider -Name[he]=ShareProvider -Name[hr]=ShareProvider -Name[hu]=ShareProvider -Name[ia]=Fornitor compartite -Name[id]=Penyedia-Berbagi -Name[is]=ShareProvider -Name[it]=Fornitore di condivisione -Name[ja]=ShareProvider -Name[kk]=Ортақтастыру қызметтер -Name[km]=ShareProvider -Name[ko]=ShareProvider -Name[lt]=ShareProvider -Name[lv]=ShareProvider -Name[mr]=सेवा पुरवठाकर्ता -Name[nb]=Dele-tjenester -Name[nds]=Apenmaakdeenstanbeder -Name[nl]=ShareProvider -Name[nn]=ShareProvider -Name[pa]=ShareProvider -Name[pl]=Dostawca udostępniania -Name[pt]=Fornecedor de Partilha -Name[pt_BR]=Provedor de compartilhamento -Name[ro]=FurnizorPartajare -Name[ru]=Служба обмена информацией -Name[sk]=Poskytovateľ zdieľania -Name[sl]=ShareProvider -Name[sr]=Добављач дељења -Name[sr@ijekavian]=Добављач дијељења -Name[sr@ijekavianlatin]=Dobavljač dijeljenja -Name[sr@latin]=Dobavljač deljenja -Name[sv]=Delningstjänst -Name[th]=ผู้ให้ใช้งานร่วมกัน -Name[tr]=SağlayıcıyıPaylaş -Name[ug]=ھەمبەھىر تەمىنلىگۈچى -Name[uk]=Служба оприлюднення -Name[wa]=Ahesseu d' pårtaedje -Name[x-test]=xxShareProviderxx -Name[zh_CN]=共享提供者 -Name[zh_TW]=分享服務提供者 -Comment=Share Package Structure -Comment[ar]=شارك بناء الحزمة -Comment[bs]=Struktura paketa dijeljenja -Comment[ca]=Estructura de paquet de compartició -Comment[ca@valencia]=Estructura de paquet de compartició -Comment[cs]=Struktura sdíleného balíčku -Comment[da]=Pakkestruktur for deling -Comment[de]=Paketstruktur bereitstellen -Comment[el]=Δομή πακέτου κοινής χρήσης -Comment[en_GB]=Share Package Structure -Comment[es]=Estructura del paquete de compartición -Comment[et]=Paketistruktuuri jagamine -Comment[eu]=Partekatze-paketearen egitura -Comment[fi]=Jaa pakettirakenne -Comment[fr]=Structure du paquet de partage -Comment[gl]=Estrutura de paquete Share -Comment[he]=Share Package Structure -Comment[hr]=Podijeli strukturu paketa -Comment[hu]=Csomagstruktúra megosztása -Comment[ia]=Share Package Structure (Structura de uso commun de pacchetto) -Comment[id]=Struktur Paket Berbagi -Comment[is]=Deila pakkauppbyggingu -Comment[it]=Struttura del pacchetto di condivisione -Comment[ja]=パッケージの構造を共有 -Comment[kk]=Ортақ дестелер құрылымы -Comment[km]=ចែករំលែក​រចនាសម្ព័ន្ធ​កញ្ចប់ -Comment[ko]=공유 패키지 구조 -Comment[lt]=Dalinimosi paketo struktūra -Comment[lv]=Kopīgot pakotnes struktūru -Comment[mr]=शेर पॅकेज संरचना -Comment[nb]=Del pakkestruktur -Comment[nds]=Paketstruktuur praatstellen -Comment[nl]=Structuur van delen van pakketten -Comment[nn]=Del pakkestruktur -Comment[pa]=ਪੈਕੇਜ ਢਾਂਚਾ ਸਾਂਝਾ ਕਰੋਪੈਕੇਜ ਢਾਂਚਾ ਸਾਪੈਕੇਜ ਫਪੈਕੇਜ ਟਾ -Comment[pl]=Struktura pakietu udostępniania -Comment[pt]=Estrutura do Pacote da Partilha -Comment[pt_BR]=Estrutura do pacote de compartilhamento -Comment[ro]=Partajează structura pachetului -Comment[ru]=Структура пакета с параметрами службы обмена информацией -Comment[sk]=Štruktúra balíčka pre zdieľanie -Comment[sl]=Zgradba paketa Share -Comment[sr]=Структура пакета дељења -Comment[sr@ijekavian]=Структура пакета дијељења -Comment[sr@ijekavianlatin]=Struktura paketa dijeljenja -Comment[sr@latin]=Struktura paketa deljenja -Comment[sv]=Dela paketstruktur -Comment[th]=โครงสร้างแพกเกจที่ใช้ร่วมกัน -Comment[tr]=Paket Yapısını Paylaş -Comment[ug]=ھەمبەھىر بوغچا قۇرۇلمىسى -Comment[uk]=Структура спільних пакунків -Comment[wa]=Tcherpinte do pacaedje di pårtaedje -Comment[x-test]=xxShare Package Structurexx -Comment[zh_CN]=共享包结构 -Comment[zh_TW]=分享軟體包結構 -Type=Service -X-KDE-ServiceTypes=Plasma/PackageStructure - -X-KDE-Library=plasma_packagestructure_share -X-KDE-PluginInfo-Author=Artur de Souza -X-KDE-PluginInfo-Email=asouza@kde.org -X-KDE-PluginInfo-Name=Plasma/ShareProvider -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop -X-KDE-PluginInfo-License=GPLv2+ -X-KDE-PluginInfo-EnabledByDefault=true -X-Plasma-PackageFileFilter=*.share -X-Plasma-PackageFileMimetypes=application/zip diff --git a/dataengines/share/plasma-dataengine-share.desktop b/dataengines/share/plasma-dataengine-share.desktop deleted file mode 100644 --- a/dataengines/share/plasma-dataengine-share.desktop +++ /dev/null @@ -1,131 +0,0 @@ -[Desktop Entry] -Name=Share Services -Name[ar]=خدمات المشاركة -Name[bg]=Услуги за споделяне -Name[bs]=Servisi dijeljenja -Name[ca]=Serveis de compartició -Name[ca@valencia]=Serveis de compartició -Name[cs]=Sdílené služby -Name[da]=Delingstjenester -Name[de]=Veröffentlichungsdienste -Name[el]=Υπηρεσίες κοινής χρήσης -Name[en_GB]=Share Services -Name[es]=Compartir servicios -Name[et]=Jagamisteenused -Name[eu]=Partekatze-zerbitzuak -Name[fi]=Jakamispalvelut -Name[fr]=Services de partage -Name[ga]=Seirbhísí Comhroinnt -Name[gl]=Servizos para compartir -Name[he]=שירותי שיתוף -Name[hi]=साझा सेवाएँ -Name[hr]=Servisi za dijeljenje -Name[hu]=Megosztási szolgáltatások -Name[ia]=Servicios compartite -Name[id]=Layanan Berbagi -Name[is]=Deila þjónustum -Name[it]=Servizi di condivisione -Name[ja]=サービスの共有 -Name[kk]=Ортақтастыру қызметтер -Name[km]=ចែក​រំលែក​សេវា -Name[kn]=ಹಂಚಿಕೆ ಸೇವೆಗಳು -Name[ko]=공유 서비스 -Name[lt]=Dalinimosi tarnybos -Name[lv]=Kopīgot servisus -Name[mr]=शेअर सेवा -Name[nb]=Dele-tjenester -Name[nds]=Praatstell-Deensten -Name[nl]=Services voor delen -Name[nn]=Del tenester -Name[pa]=ਸਾਂਝੀਆਂ ਸਰਵਿਸਾਂ -Name[pl]=Usługi współdzielenia -Name[pt]=Serviços de Partilha -Name[pt_BR]=Serviços de compartilhamento -Name[ro]=Servicii de partajare -Name[ru]=Обмен информацией -Name[sk]=Služby zdieľania -Name[sl]=Storitve za deljenje -Name[sr]=Сервиси дељења -Name[sr@ijekavian]=Сервиси дељења -Name[sr@ijekavianlatin]=Servisi deljenja -Name[sr@latin]=Servisi deljenja -Name[sv]=Delningstjänster -Name[th]=บริการที่ใช้งานร่วมกัน -Name[tr]=Paylaşım Servisleri -Name[ug]=مۇلازىمەتلەرنى ھەمبەھىرلە -Name[uk]=Служби оприлюднення -Name[wa]=Pårtaedjî siervices -Name[x-test]=xxShare Servicesxx -Name[zh_CN]=共享服务 -Name[zh_TW]=分享服務 -Comment=Engine to share content using different services -Comment[ar]=محرّك بمشاركة المحتوى باستخدام خدمات مختلفة -Comment[bg]=Споделяне на данни чрез различни услуги -Comment[bs]=Motor za dijeljenje sadržaja preko različitih servisa -Comment[ca]=Motor per compartir contingut utilitzant diferents serveis -Comment[ca@valencia]=Motor per compartir contingut utilitzant diferents serveis -Comment[cs]=Povolí sdílení obsahu pomocí různých služeb -Comment[da]=Motor til at dele indhold ved brug af forskellige tjenester -Comment[de]=Treiber für die Veröffentlichung von Inhalten über verschiedene Dienste -Comment[el]=Μηχανισμός για το μοίρασμα περιεχομένου χρησιμοποιώντας διαφορετικές υπηρεσίες -Comment[en_GB]=Engine to share content using different services -Comment[es]=Motor para compartir contenidos a través de diferentes servicios -Comment[et]=Eri teenuste kaudu sisu jagamise mootor -Comment[eu]=Hainbat zerbitzuren bidez edukia partekatzeko motorra -Comment[fi]=Moottori sisällön jakamiseen useissa palveluissa -Comment[fr]=Système de partage de contenus utilisant différents services -Comment[gl]=Motor para compartir contido mediante diversos servizos. -Comment[he]=מנוע לשיתוף תוכן באמצעות שירותים שונים -Comment[hr]=Mehanizam za dijeljenje sadržaja koristeći razne servise -Comment[hu]=Adatmodul tartalommegosztáshoz különböző szolgáltatások használatával -Comment[ia]=Motor pro compartir contento usante differente servicios -Comment[id]=Mesin untuk membagikan konten menggunakan beragam layanan -Comment[is]=Kerfi til að deila efni með ýmsum þjónustum -Comment[it]=Motore per condividere del contenuto usando diversi servizi -Comment[ja]=様々なサービスを用いたコンテンツ共有エンジン -Comment[kk]=Түрлі қызметтер көмегімен мазмұның ортақ қылу тетігі -Comment[km]=ម៉ាស៊ីន​ដើម្បី​ចែករំលែក​មាតិកា​ ដោយ​ប្រើ​សេវា​ផ្សេងៗ​គ្នា -Comment[ko]=컨텐츠 공유 서비스 -Comment[lt]=Turinio dalinimosi, naudojant įvairias tarnybas, modulis -Comment[lv]=Dzinējs satura kopīgošanai, izmantojot atšķirīgus servisus -Comment[mr]=विविध सेवा वापरुन मजकूर शेअर करण्याचे इंजिन -Comment[nb]=Motor for å dele innhold ved bruk av forskjellige tjenester -Comment[nds]=Programmkarn för Deensten för't Praatstellen vun Inholden -Comment[nl]=Engine om inhoud te delen met gebruik van verschillende services -Comment[nn]=Motor for å dela innhald ved bruk av ymse tenester -Comment[pa]=ਵੱਖ ਵੱਖ ਸਰਵਿਸਾਂ ਵਿੱਚ ਸਮਗੱਰੀ ਸਾਂਝੀ ਕਰਨ ਲਈ ਇੰਜਣ -Comment[pl]=Silnik do udostępniania zawartości przy użyciu rożnych usług -Comment[pt]=Um motor para partilhar conteúdos através de vários serviços -Comment[pt_BR]=Um mecanismo para compartilhar conteúdos usando diferentes serviços -Comment[ro]=Motor pentru partajarea conținutului folosind diferite servicii -Comment[ru]=Источник данных для обмена содержимым между людьми, используя различные интернет-службы -Comment[sk]=Nástroj na zdieľanie obsahu pomocou rôznych služieb -Comment[sl]=Pogon za deljenje vsebin prek različnih storitev -Comment[sr]=Мотор за дељење садржаја преко различитих сервиса -Comment[sr@ijekavian]=Мотор за дијељење садржаја преко различитих сервиса -Comment[sr@ijekavianlatin]=Motor za dijeljenje sadržaja preko različitih servisa -Comment[sr@latin]=Motor za deljenje sadržaja preko različitih servisa -Comment[sv]=Gränssnitt för att dela innehåll genom att använda olika tjänster -Comment[th]=กลไกการแลกเปลี่ยนเนื้อหาบนบริการที่แตกต่างกัน -Comment[tr]=Farklı servisleri kullanarak içerik paylaşmayı sağlayan motor -Comment[ug]=ئوخشىمىغان مۇلازىمەتلەرنى ئىشلىتىپ مەزمۇنلارنى ھەمبەھىرلەيدىغان ماتور -Comment[uk]=Рушій спільного використання даних різними службами -Comment[vi]=Cơ chế chia sẻ nội dung thông qua các dịch vụ -Comment[wa]=Moteur po pårtaedjî l' ådvins tot s' siervant d' diferins siervices -Comment[x-test]=xxEngine to share content using different servicesxx -Comment[zh_CN]=用不同服务共享内容的引擎 -Comment[zh_TW]=用不同的服務來分享內容的引擎 -Type=Service -X-KDE-ServiceTypes=Plasma/DataEngine -Icon=document-share - -X-KDE-Library=plasma_engine_share -X-KDE-PluginInfo-Author=Artur de Souza -X-KDE-PluginInfo-Email=asouza@kde.org -X-KDE-PluginInfo-Name=org.kde.plasma.dataengine.share -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop -X-KDE-PluginInfo-Category=Utilities -X-KDE-PluginInfo-Depends= -X-KDE-PluginInfo-License=LGPL -X-KDE-PluginInfo-EnabledByDefault=true diff --git a/dataengines/share/share.operations b/dataengines/share/share.operations deleted file mode 100644 --- a/dataengines/share/share.operations +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/dataengines/share/shareengine.cpp b/dataengines/share/shareengine.cpp deleted file mode 100644 --- a/dataengines/share/shareengine.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/*************************************************************************** - * Copyright 2010 Artur Duque de Souza * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * - ***************************************************************************/ - -#include -#include -#include - -#include - -#include "shareengine.h" -#include "shareservice.h" - - -ShareEngine::ShareEngine(QObject *parent, const QVariantList &args) - : Plasma::DataEngine(parent, args) -{ - Q_UNUSED(args); - init(); -} - -void ShareEngine::init() -{ - connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), - this, SLOT(updatePlugins(QStringList))); - updatePlugins(QStringList() << QStringLiteral("services")); -} - -void ShareEngine::updatePlugins(const QStringList &changes) -{ - if (!changes.contains(QStringLiteral("services"))) { - return; - } - - removeAllSources(); - - KService::List services = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/ShareProvider")); - QMultiMap sortedServices; - foreach (KService::Ptr service, services) { - sortedServices.insert(service->property(QStringLiteral("X-KDE-Priority")).toInt(), service); - } - - QMapIterator it(sortedServices); - it.toBack(); - QHash mimetypes; - while (it.hasPrevious()) { - it.previous(); - KService::Ptr service = it.value(); - const QString pluginName = - service->property(QStringLiteral("X-KDE-PluginInfo-Name"), QVariant::String).toString(); - - const QStringList pluginMimeTypes = - service->property(QStringLiteral("X-KDE-PlasmaShareProvider-MimeType"), QVariant::StringList).toStringList(); - - if (pluginName.isEmpty() || pluginMimeTypes.isEmpty()) { - continue; - } - - // create the list of providers - Plasma::DataEngine::Data data; - data.insert(QStringLiteral("Name"), service->name()); - data.insert(QStringLiteral("Service Id"), service->storageId()); - data.insert(QStringLiteral("Mimetypes"), pluginMimeTypes); - setData(pluginName, data); - - // create the list of providers by type - foreach (const QString &pluginMimeType, pluginMimeTypes) { - mimetypes[pluginMimeType].append(pluginName); - } - } - - - QHashIterator it2(mimetypes); - while (it2.hasNext()) { - it2.next(); - setData(QStringLiteral("Mimetypes"), it2.key(), it2.value()); - } -} - -Plasma::Service *ShareEngine::serviceForSource(const QString &source) -{ - Plasma::DataContainer *data = containerForSource(source); - - if (!data) { - return Plasma::DataEngine::serviceForSource(source); - } - - if (source.compare(QLatin1String("mimetype"), Qt::CaseInsensitive) == 0) { - return Plasma::DataEngine::serviceForSource(source); - } - - const QString id = data->data().value(QStringLiteral("Service Id")).toString(); - if (id.isEmpty()) { - return Plasma::DataEngine::serviceForSource(source); - } - - ShareService *service = new ShareService(this); - service->setDestination(id); - return service; -} - -K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(share, ShareEngine, "plasma-dataengine-share.json") - -#include "shareengine.moc" - diff --git a/dataengines/share/shareprovider.h b/dataengines/share/shareprovider.h deleted file mode 100644 --- a/dataengines/share/shareprovider.h +++ /dev/null @@ -1,101 +0,0 @@ -/*************************************************************************** - * Copyright 2010 Artur Duque de Souza * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * - ***************************************************************************/ - -#ifndef SHAREPROVIDER_H -#define SHAREPROVIDER_H - -#include -#include -#include -#include -#include - -#include - -namespace KJSEmbed { class Engine; } - -class ShareProvider : public QObject -{ - Q_OBJECT - -public: - explicit ShareProvider(KJSEmbed::Engine* engine, QObject *parent = nullptr); - - QString method() const; - void setMethod(const QString &method); - - QUrl url() const; - void setUrl(const QString &url); - - void addPostFile(const QString &contentKey, const QVariant &content); - -Q_SIGNALS: - void readyToPublish(); - void finished(const QString &url); - void finishedError(const QString &msg); - -public Q_SLOTS: - // ###: Function to return the content so the plugin can - // play with it before publishing? - - // helper methods - void publish(); - QString parseXML(const QString &key, const QString &data); - void addQueryItem(const QString &key, const QString &value); - void addPostItem(const QString &key, const QString &value, - const QString &contentType); - - // result methods - void success(const QString &url); - void error(const QString &msg); - void redirected(KIO::Job *job, const QUrl &from); - -protected Q_SLOTS: - // Q_SLOTS for kio - void mimetypeJobFinished(KJob *job); - void openFile(KIO::Job *job); - void finishedContentData(KIO::Job *job, const QByteArray &data); - void finishedPublish(KJob *job); - void readPublishData(KIO::Job *job, const QByteArray &data); - -protected: - void finishHeader(); - -private: - void publishUrl(const QUrl& url); - void uploadData(const QByteArray &data); - - QString m_content; - QString m_contentKey; - QString m_mimetype; - - bool m_isBlob; - bool m_isPost; - - QUrl m_url; - QUrl m_service; - - QByteArray m_data; - QByteArray m_buffer; - QByteArray m_boundary; - - KJSEmbed::Engine* m_engine; -}; - -#endif // SHAREPROVIDER diff --git a/dataengines/share/shareprovider.cpp b/dataengines/share/shareprovider.cpp deleted file mode 100644 --- a/dataengines/share/shareprovider.cpp +++ /dev/null @@ -1,371 +0,0 @@ -/*************************************************************************** - * Copyright 2010 Artur Duque de Souza * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * - ***************************************************************************/ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "shareprovider.h" - -ShareProvider::ShareProvider(KJSEmbed::Engine* engine, QObject *parent) - : QObject(parent), m_isBlob(false), m_isPost(true), - m_engine(engine) -{ - // Just make the boundary random part long enough to be sure - // it's not inside one of the arguments that we are sending - m_boundary = "----------"; - m_boundary += KRandom::randomString(55).toLatin1(); -} - -QString ShareProvider::method() const -{ - if (!m_isPost) { - return QStringLiteral("GET"); - } - return QStringLiteral("POST"); -} - -void ShareProvider::setMethod(const QString &method) -{ - if (method == QLatin1String("GET")) { - m_isPost = false; - } else { - m_isPost = true; - } -} - -QUrl ShareProvider::url() const -{ - // the url that is set in this provider - return m_url; -} - -void ShareProvider::setUrl(const QString &url) -{ - // set the provider's url - m_url = QUrl(url); - m_service = m_url; -} - -QString ShareProvider::parseXML(const QString &key, const QString &data) -{ - // this method helps plugins to parse results from webpages - QXmlStreamReader xml(data); - if (xml.hasError()) { - return QString(); - } - - while (!xml.atEnd()) { - xml.readNext(); - - if (xml.name() == key) { - QString url = xml.readElementText(); - return url; - } - } - - return QString(); -} - -void ShareProvider::addPostItem(const QString &key, const QString &value, - const QString &contentType) -{ - if (!m_isPost) - return; - - // add a pair in a post form - QByteArray str; - QString length = QString::number(value.length()); - - str += "--"; - str += m_boundary; - str += "\r\n"; - - if (!key.isEmpty()) { - str += "Content-Disposition: form-data; name=\""; - str += key.toLatin1(); - str += "\"\r\n"; - } - - if (!contentType.isEmpty()) { - str += "Content-Type: " + QByteArray(contentType.toLatin1()); - str += "\r\n"; - str += "Mime-version: 1.0 "; - str += "\r\n"; - } - - str += "Content-Length: "; - str += length.toLatin1(); - str += "\r\n\r\n"; - str += value.toUtf8(); - - m_buffer.append(str); - m_buffer.append("\r\n"); -} - -void ShareProvider::addPostFile(const QString &contentKey, const QVariant &content) -{ - // add a file in a post form (gets it using KIO) - m_contentKey = contentKey; - - if(content.type() == QVariant::String) { - m_content = content.toString(); - addPostItem(m_contentKey, m_content, QStringLiteral("text/plain")); - addQueryItem(m_contentKey, m_content); - emit readyToPublish(); - } else if(content.type() == QVariant::Url) { - publishUrl(content.toUrl()); - } else if(content.type() == QVariant::Image) { - QTemporaryFile* file = new QTemporaryFile(QStringLiteral("shareimage-XXXXXX.png"), this); - bool b = file->open(); - Q_ASSERT(b); - file->close(); - - QImage image = content.value(); - b = image.save(file->fileName()); - Q_ASSERT(b); - publishUrl(QUrl::fromLocalFile(file->fileName())); - } else if(content.type() == QVariant::Pixmap) { - QTemporaryFile* file = new QTemporaryFile(QStringLiteral("sharepixmap-XXXXXX.png"), this); - bool b = file->open(); - Q_ASSERT(b); - file->close(); - - QPixmap image = content.value(); - b = image.save(file->fileName()); - Q_ASSERT(b); - publishUrl(QUrl::fromLocalFile(file->fileName())); - } -} - -void ShareProvider::publishUrl(const QUrl& url) -{ - m_content = url.toString(); - - KIO::MimetypeJob *mjob = KIO::mimetype(url, KIO::HideProgressInfo); - connect(mjob, &KJob::finished, this, &ShareProvider::mimetypeJobFinished); -} - -void ShareProvider::mimetypeJobFinished(KJob *job) -{ - KIO::MimetypeJob *mjob = qobject_cast(job); - if (!job) { - return; - } - - if (mjob->error()) { - qWarning() << "error when figuring out the file type"; - return; - } - - // It's a valid file because there were no errors - m_mimetype = mjob->mimetype(); - if (m_mimetype.isEmpty()) { - // if we ourselves can't determine the mime of the file, - // very unlikely the remote site will be able to identify it - error(i18n("Could not detect the file's mimetype")); - return; - } - - // If it's not text then we should handle it later - if (!m_mimetype.startsWith(QLatin1String("text/"))) - m_isBlob = true; - - // try to open the file - KIO::FileJob *fjob = KIO::open(QUrl(m_content), QIODevice::ReadOnly); - connect(fjob, &KIO::FileJob::open, this, &ShareProvider::openFile); -} - -void ShareProvider::openFile(KIO::Job *job) -{ - // finished opening the file, now try to read it's content - KIO::FileJob *fjob = static_cast(job); - fjob->read(fjob->size()); - connect(fjob, &KIO::FileJob::data, - this, &ShareProvider::finishedContentData); -} - -void ShareProvider::finishedContentData(KIO::Job *job, const QByteArray &data) -{ - // Close the job as we don't need it anymore. - // NOTE: this is essential to ensure the job gets de-scheduled and deleted! - job->disconnect(this); - qobject_cast(job)->close(); - - if (data.length() == 0) { - error(i18n("It was not possible to read the selected file")); - return; - } - uploadData(data); -} - -void ShareProvider::uploadData(const QByteArray& data) -{ - if (!m_isBlob) { - // it's just text and we can return here using data() - addPostItem(m_contentKey, QString::fromLocal8Bit(data), QStringLiteral("text/plain")); - addQueryItem(m_contentKey, QString::fromLocal8Bit(data)); - emit readyToPublish(); - return; - } - - // Add the special http post stuff with the content of the file - QByteArray str; - const QString fileSize = QString::number(data.size()); - str += "--"; - str += m_boundary; - str += "\r\n"; - str += "Content-Disposition: form-data; name=\""; - str += m_contentKey.toLatin1(); - str += "\"; "; - str += "filename=\""; - str += QFile::encodeName(QUrl(m_content).fileName()).replace(".tmp", ".jpg"); - str += "\"\r\n"; - str += "Content-Length: "; - str += fileSize.toLatin1(); - str += "\r\n"; - str += "Content-Type: "; - str += m_mimetype.toLatin1(); - str += "\r\n\r\n"; - - m_buffer.append(str); - m_buffer.append(data); - m_buffer.append("\r\n"); - - // tell the world that we are ready to publish - emit readyToPublish(); -} - -void ShareProvider::readPublishData(KIO::Job *job, const QByteArray &data) -{ - Q_UNUSED(job); - m_data.append(data); -} - -void ShareProvider::finishedPublish(KJob *job) -{ - Q_UNUSED(job); - if (m_data.length() == 0) { - error(i18n("Service was not available")); - return; - } - - // process data. should be interpreted by the plugin. - // plugin must call the right slots after processing the data. - KJS::List kjsargs; - kjsargs.append( KJSEmbed::convertToValue(m_engine->interpreter()->execState(), m_data) ); - m_engine->callMethod("handleResultData", kjsargs); -} - -void ShareProvider::finishHeader() -{ - QByteArray str; - str += "--"; - str += m_boundary; - str += "--"; - m_buffer.append(str); -} - -void ShareProvider::addQueryItem(const QString &key, const QString &value) -{ - // just add the item to the query's URL - QUrlQuery uq(m_url); - uq.addQueryItem(key, value); - m_url.setQuery(uq); -} - -void ShareProvider::publish() -{ - if (m_url.isEmpty()) { - emit finishedError(i18n("You must specify a URL for this service")); - } - - // clear the result data before publishing - m_data.clear(); - - // finish the http form - if (m_isBlob) { - finishHeader(); - } - - // Multipart is used to upload files - KIO::TransferJob *tf; - if (m_isBlob) { - tf = KIO::http_post(m_service, m_buffer, KIO::HideProgressInfo); - tf->addMetaData(QStringLiteral("content-type"),"Content-Type: multipart/form-data; boundary=" + m_boundary); - } else { - if (m_isPost) { - tf = KIO::http_post(m_service, - m_url.query(QUrl::FullyEncoded).toLatin1(), KIO::HideProgressInfo); - tf->addMetaData(QStringLiteral("content-type"), QStringLiteral("Content-Type: application/x-www-form-urlencoded")); - } else { - QUrl url = m_service; - url.setQuery(m_url.query()); - tf = KIO::get(url); - } - } - - connect(tf, &KIO::TransferJob::data, - this, &ShareProvider::readPublishData); - connect(tf, &KJob::result, this, &ShareProvider::finishedPublish); - connect(tf, &KIO::TransferJob::redirection, - this, &ShareProvider::redirected); -} - -void ShareProvider::redirected(KIO::Job *job, const QUrl &to) -{ - Q_UNUSED(job) - const QUrl toUrl(to); - const QUrl serviceUrl(m_service); - - const QString toString(toUrl.toString(QUrl::StripTrailingSlash)); - const QString serviceString(serviceUrl.toString(QUrl::StripTrailingSlash)); - - if (toString == serviceString) { - return; - } - - KJS::List kjsargs; - kjsargs.append( KJSEmbed::convertToValue(m_engine->interpreter()->execState(), toString) ); - m_engine->callMethod("handleRedirection", kjsargs); -} - -void ShareProvider::success(const QString &url) -{ - // notify the service that it worked and the result url - emit finished(url); -} - -void ShareProvider::error(const QString &msg) -{ - // notify the service that it didn't work and the error msg - emit finishedError(msg); -} - - diff --git a/dataengines/share/shareservice.cpp b/dataengines/share/shareservice.cpp deleted file mode 100644 --- a/dataengines/share/shareservice.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/*************************************************************************** - * Copyright 2010 Artur Duque de Souza * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * - ***************************************************************************/ - -#include -#include -#include - -#include -#include - -#include - - -#include "shareservice.h" -#include "shareprovider.h" -#include "config-workspace.h" - -ShareService::ShareService(ShareEngine *engine) - : Plasma::Service(engine) -{ - setName(QStringLiteral("share")); -} - -Plasma::ServiceJob *ShareService::createJob(const QString &operation, - QMap ¶meters) -{ - return new ShareJob(destination(), operation, parameters, this); -} - -ShareJob::ShareJob(const QString &destination, const QString &operation, - QMap ¶meters, QObject *parent) - : Plasma::ServiceJob(destination, operation, parameters, parent), - m_engine(new KJSEmbed::Engine()), m_provider(nullptr) -{ -} - -ShareJob::~ShareJob() -{ - delete m_provider; -} - -void ShareJob::start() -{ - //KService::Ptr service = KService::serviceByStorageId("plasma-share-pastebincom.desktop"); - KService::Ptr service = KService::serviceByStorageId(destination()); - if (!service) { - showError(i18n("Could not find the provider with the specified destination")); - return; - } - - QString pluginName = - service->property(QStringLiteral("X-KDE-PluginInfo-Name"), QVariant::String).toString(); - - const QString path = - QStandardPaths::locate(QStandardPaths::GenericDataLocation, PLASMA_RELATIVE_DATA_INSTALL_DIR "/shareprovider/" + pluginName + '/' ); - - if (path.isEmpty()) { - showError(i18n("Invalid path for the requested provider")); - return; - } - - m_package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/ShareProvider")); - m_package.setPath(path); - if (m_package.isValid()) { - const QString mainscript = m_package.filePath("mainscript"); - - m_provider = new ShareProvider(m_engine.data(), this); - connect(m_provider, &ShareProvider::readyToPublish, this, &ShareJob::publish); - connect(m_provider, &ShareProvider::finished, - this, &ShareJob::showResult); - connect(m_provider, &ShareProvider::finishedError, - this, &ShareJob::showError); - - m_engine->addObject(m_provider, "provider"); - - // set the main script file and load it - KJSEmbed::Engine::ExitStatus status = m_engine->runFile(mainscript); - - // check for any errors - if(status == KJSEmbed::Engine::Failure) { - showError(i18n("Error trying to execute script")); - return; - } - - KJS::ExecState* execState = m_engine->interpreter()->execState(); - KJS::JSGlobalObject* scriptObject = m_engine->interpreter()->globalObject(); - - // do the work together with the loaded plugin - if (!scriptObject->hasProperty(execState, "url") || !scriptObject->hasProperty(execState, "contentKey") || - !scriptObject->hasProperty(execState, "setup")) { - showError(i18n("Could not find all required functions")); - return; - } - - // call the methods from the plugin - const QString url = m_engine->callMethod("url")->toString(execState).qstring(); - m_provider->setUrl(url); - - // setup the method (get/post) - QVariant vmethod; - if (scriptObject->hasProperty(execState, "method")) { - vmethod = m_engine->callMethod("method")->toString(execState).qstring(); - } - - // default is POST (if the plugin does not specify one method) - const QString method = vmethod.isValid() ? vmethod.toString() : QStringLiteral("POST"); - m_provider->setMethod(method); - - // setup the provider - m_engine->callMethod("setup"); - - // get the content from the parameters, set the url and add the file - // then we can wait the signal to publish the information - const QString contentKey = m_engine->callMethod("contentKey")->toString(execState).qstring(); - - QVariant contents = parameters()[QStringLiteral("content")]; - if(contents.type() == QVariant::List) { - QVariantList list = contents.toList(); - if (list.size() == 1) { - contents = list.first(); - } - } - m_provider->addPostFile(contentKey, contents); - } -} - -void ShareJob::publish() -{ - m_provider->publish(); -} - -void ShareJob::showResult(const QString &url) -{ - setResult(url); -} - -void ShareJob::showError(const QString &message) -{ - QString errorMsg = message; - if (errorMsg.isEmpty()) { - errorMsg = i18n("Unknown Error"); - } - - setError(1); - setErrorText(message); - emitResult(); -} - - diff --git a/dataengines/soliddevice/plasma-dataengine-soliddevice.desktop b/dataengines/soliddevice/plasma-dataengine-soliddevice.desktop --- a/dataengines/soliddevice/plasma-dataengine-soliddevice.desktop +++ b/dataengines/soliddevice/plasma-dataengine-soliddevice.desktop @@ -108,7 +108,7 @@ Comment[km]=ទិន្នន័យ​ឧបករណ៍តាម​រយៈ Solid​ Comment[kn]=ಸಾಲಿಡ್‌ನ ಮೂಲಕ ಸಾಧನ ದತ್ತಾಂಶ Comment[ko]=Solid를 통한 장치 데이터 -Comment[lt]=Solid įrenginių duomenys +Comment[lt]=Įrenginių duomenys per Solid Comment[lv]=Ierīču dati no Solid Comment[mai]=सालिड सँ युक्ति आंकड़ा Comment[mk]=Податоци за уреди преку Солид @@ -132,7 +132,6 @@ Comment[sr@ijekavianlatin]=Podaci o uređajima preko Solida Comment[sr@latin]=Podaci o uređajima preko Solida Comment[sv]=Enhetsdata via Solid -Comment[tg]=Данные SolidDevice для плазмоидов Comment[th]=ข้อมูลของอุปกรณ์ผ่านทาง Solid Comment[tr]=Solid üzerinden aygıt verisi Comment[ug]=Solid ئارقىلىق ئۈسكۈنە سانلىق-مەلۇماتىغا ئېرىشىدۇ diff --git a/dataengines/soliddevice/soliddeviceengine.cpp b/dataengines/soliddevice/soliddeviceengine.cpp --- a/dataengines/soliddevice/soliddeviceengine.cpp +++ b/dataengines/soliddevice/soliddeviceengine.cpp @@ -533,7 +533,7 @@ void SolidDeviceEngine::deviceChanged(const QMap &props) { Solid::GenericInterface * iface = qobject_cast(sender()); - if (iface && iface->isValid() && props.contains(QStringLiteral("Size")) && iface->property(QStringLiteral("Size")).toInt() > 0) { + if (iface && iface->isValid() && props.contains(QLatin1String("Size")) && iface->property(QStringLiteral("Size")).toInt() > 0) { const QString udi = qobject_cast(iface)->property("udi").toString(); if (populateDeviceData(udi)) forceImmediateUpdateOfAllVisualizations(); diff --git a/dataengines/statusnotifieritem/plasma-dataengine-statusnotifieritem.desktop b/dataengines/statusnotifieritem/plasma-dataengine-statusnotifieritem.desktop --- a/dataengines/statusnotifieritem/plasma-dataengine-statusnotifieritem.desktop +++ b/dataengines/statusnotifieritem/plasma-dataengine-statusnotifieritem.desktop @@ -1,10 +1,11 @@ [Desktop Entry] Name=Status Notifier Information Name[ar]=معلومات مُخطِر الحالة +Name[ast]=Información del avisador d'estaos Name[bs]=Podaci izveštavača o stanju Name[ca]=Informació del notificador d'estats Name[ca@valencia]=Informació del notificador d'estats -Name[cs]=Informace o oznamovači stavu +Name[cs]=Informace o upozornění stavu Name[da]=Information om statusbekendtgørelse Name[de]=Status-Informationen Name[el]=Πληροφορίες για τις ειδοποιήσεις κατάστασης @@ -72,7 +73,7 @@ Comment[eu]=Aplikazioen egoerari buruzko informazioa emateko motorra, egoera-jakinarazlearen protokoloan oinarritua. Comment[fi]=Kone sovelusten tilatiedoille, perustuu tilailmoitinyhteyskäytäntöön. Comment[fr]=Moteur d'informations sur l'état des applications utilisant le protocole de notification d'état. -Comment[gl]=Motor para información do estado dos aplicativos, baseado no protocolo Status Notifier. +Comment[gl]=Motor para información do estado das aplicacións, baseado no protocolo Status Notifier. Comment[he]=מנוע עבור מידע אודות מצב של יישומים, המתבסס על הפרוטוקול של Status Notifier. Comment[hr]=Mehanizam za statusne informacije aplikacija baziran na protokolu glasnika stanja. Comment[hu]=Az állaporértesítés protokollon alapuló modul az alkalmazások állapotértesítési információihoz. @@ -84,7 +85,7 @@ Comment[kk]=Күй-жайы туралы құлақтандыру (Status Notifier) протоколын негіздеген қолданбаларға арналған тетік. Comment[km]=ម៉ាស៊ីន​សម្រាប់​ព័ត៌មាន​ស្ថានភាព​របស់​​កម្មវិធី ដែលមាន​មូលដ្ឋាន​លើ​ពិធីការ​កម្មវិធី​ជូនដំណឹង​ស្ថានភាព ។ Comment[ko]=상태 알림 프로토콜을 사용하는 프로그램 상태 정보 엔진입니다. -Comment[lt]=Programų būsenos informacijos varikliukas, pagrįstas būsenos pranešėjo protokolu. +Comment[lt]=Programų būsenos informacijos variklis, pagrįstas būsenos pranešėjo protokolu. Comment[lv]=Programmas statusa informācijas dzinējs, kas ir bāzēts uz sistēmas paziņojumu protokolu. Comment[mr]=अनुप्रयोग स्थिती माहिती साठी स्थिती निदर्शक शिष्टाचारावर आधारित इंजिन Comment[nb]=Motor for programstatus-informasjon basert på Status Notifier-protokollen. @@ -96,7 +97,7 @@ Comment[pt]=Motor para a informação do estado das aplicações, baseado no protocolo do Item de Notificação do Estado. Comment[pt_BR]=Mecanismo para informação de status de aplicativos baseado no protocolo do Notificador de Status. Comment[ro]=Motor pentru informațiile de stare ale aplicațiilor, bazat pe protocolul Notificator de Stare. -Comment[ru]=Движок данных о состоянии программ, основанный на протоколе уведомлений о состоянии. +Comment[ru]=Источник данных о состоянии программ, основанный на протоколе уведомлений о состоянии. Comment[si]=තත්ව දැනුම්දීම් නියමාවලිය මත පදනම් වූ යෙදුම්වල තත්ව තොරතුරු සඳහා එන්ජිම. Comment[sk]=Nástroj pre informácie o stave aplikácie, založený na protokole o oznámení stavu. Comment[sl]=Pogon za podatke o stanju programov, ki temelji na protokolu obvestilnika o stanju. diff --git a/dataengines/statusnotifieritem/statusnotifieritemjob.cpp b/dataengines/statusnotifieritem/statusnotifieritemjob.cpp --- a/dataengines/statusnotifieritem/statusnotifieritemjob.cpp +++ b/dataengines/statusnotifieritem/statusnotifieritemjob.cpp @@ -25,7 +25,7 @@ m_source(source) { connect(source, SIGNAL(contextMenuReady(QMenu*)), this, SLOT(contextMenuReady(QMenu*))); - connect(source, SIGNAL(activateResult(bool)), this, SLOT(activateCallback(bool))); + connect(source, &StatusNotifierItemSource::activateResult, this, &StatusNotifierItemJob::activateCallback); } StatusNotifierItemJob::~StatusNotifierItemJob() @@ -57,6 +57,6 @@ void StatusNotifierItemJob::contextMenuReady(QMenu *menu) { if (operationName() == QString::fromLatin1("ContextMenu")) { - setResult(qVariantFromValue((QObject*)menu)); + setResult(QVariant::fromValue((QObject*)menu)); } } diff --git a/dataengines/statusnotifieritem/statusnotifieritemsource.cpp b/dataengines/statusnotifieritem/statusnotifieritemsource.cpp --- a/dataengines/statusnotifieritem/statusnotifieritemsource.cpp +++ b/dataengines/statusnotifieritem/statusnotifieritemsource.cpp @@ -132,7 +132,7 @@ connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewAttentionIcon, this, &StatusNotifierItemSource::refreshIcons); connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewOverlayIcon, this, &StatusNotifierItemSource::refreshIcons); connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewToolTip, this, &StatusNotifierItemSource::refreshToolTip); - connect(m_statusNotifierItemInterface, SIGNAL(NewStatus(QString)), this, SLOT(syncStatus(QString))); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewStatus, this, &StatusNotifierItemSource::syncStatus); refresh(); } } @@ -303,7 +303,7 @@ overlayIcon(&icon, &overlay); } } - setData(QStringLiteral("Icon"), icon); + setData(QStringLiteral("Icon"), icon.isNull() ? QVariant() : icon); setData(QStringLiteral("IconName"), iconName); } @@ -329,7 +329,7 @@ overlayIcon(&attentionIcon, &overlay); } } - setData(QStringLiteral("AttentionIcon"), attentionIcon); + setData(QStringLiteral("AttentionIcon"), attentionIcon.isNull() ? QVariant() : attentionIcon); } //ToolTip diff --git a/dataengines/systemmonitor/plasma-dataengine-systemmonitor.desktop b/dataengines/systemmonitor/plasma-dataengine-systemmonitor.desktop --- a/dataengines/systemmonitor/plasma-dataengine-systemmonitor.desktop +++ b/dataengines/systemmonitor/plasma-dataengine-systemmonitor.desktop @@ -2,6 +2,7 @@ Name=System Monitor Name[af]=Stelsel Monitor Name[ar]=مرقاب النظام +Name[ast]=Supervisor del sistema Name[be]=Сістэмны назіральнік Name[be@latin]=Systemny nazirańnik Name[bg]=Наблюдение на системата @@ -25,7 +26,7 @@ Name[fr]=Surveillance du système Name[fy]=Systeemmonitor Name[ga]=Monatóir an Chórais -Name[gl]=Monitor do sistema +Name[gl]=Vixilante do sistema Name[gu]=સિસ્ટમ દેખરેખ Name[he]=מוניטור המערכת Name[hi]=तंत्र मॉनीटर @@ -43,7 +44,7 @@ Name[kn]=ವ್ಯವಸ್ಥೆಯ ಪ್ರದರ್ಶಕ Name[ko]=시스템 모니터 Name[ku]=Temaşekerê Pergalê -Name[lt]=Sistemos stebėtojas +Name[lt]=Sistemos prižiūryklė Name[lv]=Sistēmas monitors Name[mai]=सिस्टम मानीटर Name[mk]=Системски монитор @@ -72,7 +73,7 @@ Name[sv]=Systemövervakare Name[ta]=கணினி நோட்டம் Name[te]=సిస్టమ్ మానిటర్ -Name[tg]=Назорати система +Name[tg]=Назорати низом Name[th]=ติดตามการทำงานของระบบ Name[tr]=Sistem İzleyici Name[ug]=سىستېما كۆزەتكۈچ @@ -84,6 +85,7 @@ Name[zh_TW]=系統監視器 Comment=System status information Comment[ar]=معلومات حالة النظام +Comment[ast]=Información del estáu del sistema Comment[bg]=Данни за състоянието на системата Comment[bn]=সিস্টেম স্ট্যাটাস তথ্য Comment[bs]=Podaci o stanju sistema @@ -118,7 +120,7 @@ Comment[km]=ព័ត៌មាន​ស្ថានភាព​របស់​ប្រព័ន្ធ Comment[kn]=ವ್ಯವಸ್ಥೆಯ ಸ್ಥಿತಿಯ ಮಾಹಿತಿ Comment[ko]=시스템 상태 정보 -Comment[lt]=Sistemos būklės informacija +Comment[lt]=Sistemos būsenos informacija Comment[lv]=Sistēmas statusa informācija Comment[mk]=Информации за статусот на системот Comment[ml]=സിസ്റ്റത്തിന്റെ അവസ്ഥാ വിവരം @@ -141,7 +143,6 @@ Comment[sr@ijekavianlatin]=Podaci o stanju sistema Comment[sr@latin]=Podaci o stanju sistema Comment[sv]=Information om systemstatus -Comment[tg]=Барномаи маълумоти система Comment[th]=ข้อมูลสถานะของระบบ Comment[tr]=Sistem durumu bilgileri Comment[ug]=سىستېما ھالەت ئۇچۇرى diff --git a/dataengines/time/plasma-dataengine-time.desktop b/dataengines/time/plasma-dataengine-time.desktop --- a/dataengines/time/plasma-dataengine-time.desktop +++ b/dataengines/time/plasma-dataengine-time.desktop @@ -2,6 +2,7 @@ Name=Date and Time Name[ar]=التاريخ والوقت Name[as]=তাৰিখ আৰু সময় +Name[ast]=Data y hora Name[be@latin]=Data j čas Name[bg]=Дата и час Name[bn]=তারিখ এবং সময় @@ -113,7 +114,7 @@ Comment[km]=កាលបរិច្ឆេទ និង​ពេលវេលា​តាម​តំបន់​ពេលវេលា Comment[kn]=ಕಾಲವಲಯದಿಂದ ದಿನಾಂಕ ಮತ್ತು ಸಮಯ Comment[ko]=시간대에 따른 날짜와 시간 -Comment[lt]=Data ir laikas pagal laiko juostas +Comment[lt]=Data ir laikas pagal laiko juostą Comment[lv]=Datums un laiks pa laika joslām Comment[mk]=Датум и време според временска зона Comment[ml]=തീയതിയും സമയവും സമയമേഖലയനുസരിച്ചു് @@ -136,7 +137,6 @@ Comment[sr@ijekavianlatin]=Datum i vrijeme po vremenskoj zoni Comment[sr@latin]=Datum i vreme po vremenskoj zoni Comment[sv]=Datum och tid enligt tidszon -Comment[tg]=Танзимоти сана ва вақт Comment[th]=วันและเวลาตามเขตเวลา Comment[tr]=Zaman dilimine göre tarih ve saat Comment[ug]=ۋاقىت رايونىغا ئاساسەن چېسلا ۋە ۋاقىت تەمىنلەيدۇ diff --git a/dataengines/time/solarsystem.h b/dataengines/time/solarsystem.h --- a/dataengines/time/solarsystem.h +++ b/dataengines/time/solarsystem.h @@ -24,11 +24,11 @@ /* * Mathematics, ideas, public domain code used for these classes from: - * http://www.stjarnhimlen.se/comp/tutorial.html - * http://www.stjarnhimlen.se/comp/riset.html - * http://www.srrb.noaa.gov/highlights/solarrise/azel.html - * http://www.srrb.noaa.gov/highlights/sunrise/sunrise.html - * http://bodmas.org/astronomy/riset.html + * https://www.stjarnhimlen.se/comp/tutorial.html + * https://www.stjarnhimlen.se/comp/riset.html + * https://www.esrl.noaa.gov/gmd/grad/solcalc/azel.html + * https://www.esrl.noaa.gov/gmd/grad/solcalc/sunrise.html + * http://web.archive.org/web/20080309162302/http://bodmas.org/astronomy/riset.html * moontool.c by John Walker * Wikipedia */ diff --git a/dataengines/time/solarsystem.cpp b/dataengines/time/solarsystem.cpp --- a/dataengines/time/solarsystem.cpp +++ b/dataengines/time/solarsystem.cpp @@ -17,14 +17,15 @@ #include "solarsystem.h" #include +#include /* * Mathematics, ideas, public domain code used for these classes from: - * http://www.stjarnhimlen.se/comp/tutorial.html - * http://www.stjarnhimlen.se/comp/riset.html - * http://www.srrb.noaa.gov/highlights/solarrise/azel.html - * http://www.srrb.noaa.gov/highlights/sunrise/sunrise.html - * http://bodmas.org/astronomy/riset.html + * https://www.stjarnhimlen.se/comp/tutorial.html + * https://www.stjarnhimlen.se/comp/riset.html + * https://www.esrl.noaa.gov/gmd/grad/solcalc/azel.html + * https://www.esrl.noaa.gov/gmd/grad/solcalc/sunrise.html + * http://web.archive.org/web/20080309162302/http://bodmas.org/astronomy/riset.html * moontool.c by John Walker * Wikipedia */ diff --git a/dataengines/weather/ions/bbcukmet/ion-bbcukmet.desktop b/dataengines/weather/ions/bbcukmet/ion-bbcukmet.desktop --- a/dataengines/weather/ions/bbcukmet/ion-bbcukmet.desktop +++ b/dataengines/weather/ions/bbcukmet/ion-bbcukmet.desktop @@ -2,42 +2,52 @@ Name=BBC Weather Name[ca]=BBC Weather Name[ca@valencia]=BBC Weather +Name[cs]=BBC Weather Name[da]=BBC Weather Name[de]=BBC-Wetterdienst Name[en_GB]=BBC Weather Name[es]=BBC Weather +Name[et]=BBC ilmateade Name[eu]=BBC Weather Name[fi]=BBC Weather Name[fr]=Météo de la BBC Name[gl]=BBC Weather +Name[hu]=BBC Weather Name[id]=Cuaca BBC Name[it]=BBC Weather Name[ko]=BBC 날씨 +Name[lt]=BBC orai +Name[lv]=BBC laikapstākļi Name[nl]=BBC Weather Name[nn]=BBC Weather Name[pl]=Pogoda BBC Name[pt]=Meteorologia da BBC Name[pt_BR]=BBC Weather Name[ru]=Погода от Би-би-си Name[sk]=BBC Weather Name[sv]=BBC Weather +Name[tg]=Обу ҳавои BBC Name[uk]=Погода BBC Name[x-test]=xxBBC Weatherxx Name[zh_CN]=BBC 天气 Name[zh_TW]=BBC 天氣 Comment=XML Data from the British Broadcasting Corporation Comment[ca]=Dades XML de la British Broadcasting Corporation Comment[ca@valencia]=Dades XML de la British Broadcasting Corporation Comment[da]=XML-data fra British Broadcasting Corporation +Comment[de]=XML-Daten von der BBC Comment[en_GB]=XML Data from the British Broadcasting Corporation Comment[es]=Datos XML de la British Broadcasting Corporation +Comment[et]=XML-andmed Briti ringhäälingukorporatsioonilt Comment[eu]=British Broadcasting Corporation-en XML datuak Comment[fi]=XML-data BBC:ltä Comment[fr]=Données XML de la British Broadcasting Corporation Comment[gl]=Datos XML da British Broadcasting Corporation +Comment[hu]=XML-adatok a British Broadcasting Corporationtől Comment[id]=Data XML dari British Broadcasting Corporation Comment[it]=Dati XML dalla British Broadcasting Corporation Comment[ko]=BBC의 XML 데이터 +Comment[lt]=XML duomenys iš BBC (British Broadcasting Corporation) Comment[nl]=XML-gegevens van de British Broadcasting Corporation Comment[nn]=XML-data frå British Broadcasting Corporation Comment[pl]=Dane XML z British Broadcasting Corporation diff --git a/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp b/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp --- a/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp +++ b/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp @@ -273,7 +273,7 @@ return true; } - XMLMapInfo& place = m_place[QStringLiteral("bbcukmet|") + sourceAction[2]]; + XMLMapInfo& place = m_place[QLatin1String("bbcukmet|") + sourceAction[2]]; // backward compatibility after rss feed url change in 2018/03 place.sourceExtraArg = sourceAction[3]; @@ -375,11 +375,11 @@ const QString fullName = result.value(QStringLiteral("fullName")).toString(); if (!id.isEmpty() && !fullName.isEmpty()) { - QString tmp = QStringLiteral("bbcukmet|") + fullName; + QString tmp = QLatin1String("bbcukmet|") + fullName; // Duplicate places can exist if (m_locations.contains(tmp)) { - tmp += QStringLiteral(" (#") + QString::number(counter) + QLatin1Char(')'); + tmp += QLatin1String(" (#") + QString::number(counter) + QLatin1Char(')'); counter++; } XMLMapInfo& place = m_place[tmp]; @@ -430,7 +430,7 @@ } // If Redirected, don't go to this routine - if (!m_locations.contains(QStringLiteral("bbcukmet|") + m_jobList[job])) { + if (!m_locations.contains(QLatin1String("bbcukmet|") + m_jobList[job])) { QByteArray *reader = m_jobHtml.value(job); if (reader) { readSearchHTMLData(m_jobList[job], *reader); @@ -671,7 +671,7 @@ parseFloat(data.temperature_C, temperature_C); data.windDirection = observeData[2].section(QLatin1Char(','), 0, 0).trimmed(); - if (data.windDirection.contains(QStringLiteral("null"))) { + if (data.windDirection.contains(QLatin1String("null"))) { data.windDirection.clear(); } @@ -925,7 +925,7 @@ // work-around for buggy observation RSS feed missing the station name QString stationName = weatherData.stationName; - if (stationName.isEmpty() || stationName == QLatin1String(",")) { + if (stationName.isEmpty() || stationName == QLatin1Char(',')) { stationName = source.section(QLatin1Char('|'), 1, 1); } diff --git a/dataengines/weather/ions/data/envcan_i18n.dat b/dataengines/weather/ions/data/envcan_i18n.dat --- a/dataengines/weather/ions/data/envcan_i18n.dat +++ b/dataengines/weather/ions/data/envcan_i18n.dat @@ -29,6 +29,7 @@ weather condition|Heavy Snow weather condition|Heavy Snow Pellets weather condition|Heavy Snowshower +weather condition|Thunderstorm weather condition|Heavy Thunderstorm with Hail weather condition|Heavy Thunderstorm with Rain weather condition|Ice Crystals @@ -80,6 +81,8 @@ weather condition|Sunny weather condition|Thunderstorm with Hail weather condition|Thunderstorm with Rain +weather condition|Thunderstorm with light rainshowers +weather condition|Thunderstorm with heavy rainshowers weather condition|Thunderstorm with Sand or Dust Storm weather condition|Thunderstorm without Precipitation weather condition|Tornado diff --git a/dataengines/weather/ions/envcan/ion-envcan.desktop b/dataengines/weather/ions/envcan/ion-envcan.desktop --- a/dataengines/weather/ions/envcan/ion-envcan.desktop +++ b/dataengines/weather/ions/envcan/ion-envcan.desktop @@ -4,7 +4,7 @@ Name[bg]=Environment Canada Name[bs]=Prirodna sredina Kanada Name[ca]=Medi ambient del Canadà -Name[ca@valencia]=Medi ambient del Canadà +Name[ca@valencia]=Medi ambient de Canadà Name[cs]=Meteorologický úřad Kanady (EC) Name[da]=Environment Canada Name[de]=Environment Canada @@ -61,7 +61,6 @@ Name[sv]=Environment Canada Name[ta]=Environment Canada Name[te]=కెనడా వాతావరణం -Name[tg]=Environment Canada Name[th]=Environment Canada Name[tr]=Environment Canada Name[ug]=كانادا مۇھىتى @@ -75,7 +74,7 @@ Comment[bg]=XML данни от Environment Canada Comment[bs]=IksML podaci Prirodne sredine Kanada Comment[ca]=Dades XML des de Medi ambient del Canadà -Comment[ca@valencia]=Dades XML des de Medi ambient del Canadà +Comment[ca@valencia]=Dades XML des de Medi ambient de Canadà Comment[cs]=XML data z meteorologického úřadu Kanady (EC) Comment[da]=XML-data fra Environment Canada Comment[de]=XML-Daten von Environment Canada @@ -132,7 +131,6 @@ Comment[sv]=XML-data från Environment Canada Comment[ta]=XML Data from Environment Canada Comment[te]=కెనడా వాతావరణం నుండి XML డాటా -Comment[tg]=Маълумоти XML аз Environment Canada Comment[th]=ข้อมูล XML จาก Environment Canada Comment[tr]=Environment Canada'dan XML Verisi Comment[ug]=كانادا مۇھىتى تەمىنلىگەن XML سانلىق-مەلۇماتى diff --git a/dataengines/weather/ions/envcan/ion_envcan.h b/dataengines/weather/ions/envcan/ion_envcan.h --- a/dataengines/weather/ions/envcan/ion_envcan.h +++ b/dataengines/weather/ions/envcan/ion_envcan.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2007-2009 by Shawn Starr * + * Copyright (C) 2007-2009,2019 by Shawn Starr * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -70,6 +70,7 @@ int forecastHumidity; }; + QString creditUrl; QString countryName; QString longTerritoryName; QString shortTerritoryName; diff --git a/dataengines/weather/ions/envcan/ion_envcan.cpp b/dataengines/weather/ions/envcan/ion_envcan.cpp --- a/dataengines/weather/ions/envcan/ion_envcan.cpp +++ b/dataengines/weather/ions/envcan/ion_envcan.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2007-2011 by Shawn Starr * + * Copyright (C) 2007-2011,2019 by Shawn Starr * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -182,8 +182,11 @@ { QStringLiteral("snow crystals"), Flurries }, { QStringLiteral("snow grains"), Flurries }, { QStringLiteral("squalls"), Snow }, + { QStringLiteral("thunderstorm"), Thunderstorm }, { QStringLiteral("thunderstorm with hail"), Thunderstorm }, { QStringLiteral("thunderstorm with rain"), Thunderstorm }, + { QStringLiteral("thunderstorm with light rainshowers"), Thunderstorm }, + { QStringLiteral("thunderstorm with heavy rainshowers"), Thunderstorm }, { QStringLiteral("thunderstorm with sand or dust storm"), Thunderstorm }, { QStringLiteral("thunderstorm without precipitation"), Thunderstorm }, { QStringLiteral("tornado"), NotAvailable }, @@ -679,7 +682,7 @@ if (xml.isStartElement()) { if (elementName == QLatin1String("license")) { - xml.readElementText(); + data.creditUrl = xml.readElementText(); } else if (elementName == QLatin1String("location")) { parseLocations(data, xml); } else if (elementName == QLatin1String("warnings")) { @@ -948,7 +951,7 @@ } else if (elementName == QLatin1String("dateTime")) { parseDateTime(data, xml); } else if (elementName == QLatin1String("condition")) { - data.condition = xml.readElementText(); + data.condition = xml.readElementText().trimmed(); } else if (elementName == QLatin1String("temperature")) { // prevent N/A text to result in 0.0 value parseFloat(data.temperature, xml); @@ -1647,8 +1650,8 @@ data.insert(QStringLiteral("Record Snowfall Unit"), KUnitConversion::Centimeter); } - data.insert(QStringLiteral("Credit"), i18nc("credit line, keep string short", "Data from Environment\302\240Canada")); - + data.insert(QStringLiteral("Credit"), i18nc("credit line, keep string short", "Data from Environment and Climate Change\302\240Canada")); + data.insert(QStringLiteral("Credit Url"), weatherData.creditUrl); setData(source, data); } diff --git a/dataengines/weather/ions/ion.h b/dataengines/weather/ions/ion.h --- a/dataengines/weather/ions/ion.h +++ b/dataengines/weather/ions/ion.h @@ -119,10 +119,10 @@ public: - enum ConditionIcons { ClearDay = 1, FewCloudsDay, PartlyCloudyDay, Overcast, + enum ConditionIcons { ClearDay = 1, ClearWindyDay, FewCloudsDay, FewCloudsWindyDay, PartlyCloudyDay, PartlyCloudyWindyDay, Overcast, OvercastWindy, Rain, LightRain, Showers, ChanceShowersDay, Thunderstorm, Hail, - Snow, LightSnow, Flurries, FewCloudsNight, ChanceShowersNight, - PartlyCloudyNight, ClearNight, Mist, Haze, FreezingRain, + Snow, LightSnow, Flurries, FewCloudsNight, FewCloudsWindyNight, ChanceShowersNight, + PartlyCloudyNight, PartlyCloudyWindyNight, ClearNight, ClearWindyNight, Mist, Haze, FreezingRain, RainSnow, FreezingDrizzle, ChanceThunderstormDay, ChanceThunderstormNight, ChanceSnowDay, ChanceSnowNight, NotAvailable }; diff --git a/dataengines/weather/ions/ion.cpp b/dataengines/weather/ions/ion.cpp --- a/dataengines/weather/ions/ion.cpp +++ b/dataengines/weather/ions/ion.cpp @@ -146,12 +146,20 @@ switch (condition) { case ClearDay: return QStringLiteral("weather-clear"); + case ClearWindyDay: + return QStringLiteral("weather-clear-wind"); case FewCloudsDay: return QStringLiteral("weather-few-clouds"); + case FewCloudsWindyDay: + return QStringLiteral("weather-few-clouds-wind"); case PartlyCloudyDay: return QStringLiteral("weather-clouds"); + case PartlyCloudyWindyDay: + return QStringLiteral("weather-clouds-wind"); case Overcast: return QStringLiteral("weather-overcast"); + case OvercastWindy: + return QStringLiteral("weather-overcast-wind"); case Rain: return QStringLiteral("weather-showers"); case LightRain: @@ -180,10 +188,16 @@ return QStringLiteral("weather-snow-rain"); case FewCloudsNight: return QStringLiteral("weather-few-clouds-night"); + case FewCloudsWindyNight: + return QStringLiteral("weather-few-clouds-wind-night"); case PartlyCloudyNight: return QStringLiteral("weather-clouds-night"); + case PartlyCloudyWindyNight: + return QStringLiteral("weather-clouds-wind-night"); case ClearNight: return QStringLiteral("weather-clear-night"); + case ClearWindyNight: + return QStringLiteral("weather-clear-wind-night"); case Mist: return QStringLiteral("weather-fog"); case Haze: diff --git a/dataengines/weather/ions/noaa/ion-noaa.desktop b/dataengines/weather/ions/noaa/ion-noaa.desktop --- a/dataengines/weather/ions/noaa/ion-noaa.desktop +++ b/dataengines/weather/ions/noaa/ion-noaa.desktop @@ -34,7 +34,7 @@ Name[km]=សេវា​អាកាសធាតុ​ជាតិ​របស់ NOAA Name[kn]=NOAA ದ ರಾಷ್ಟ್ರೀಯ ಹವಾಮಾನ ಸೇವೆ Name[ko]=NOAA 미국 기상 서비스 -Name[lt]=NOAA's National Weather Service +Name[lt]=NOAA nacionalinė orų tarnyba Name[lv]=NOAA's Nacionālais Laikapstākļu Serviss Name[mk]=Национален сервис за време на NOAA Name[ml]=നോആ-യുടെ ദേശീയ കാലവസ്ഥാ സേവനം @@ -61,7 +61,6 @@ Name[sv]=NOAA:s nationella vädertjänst Name[ta]=NOAA's National Weather Service Name[te]=NOAA యొక్క జాతీయ వాతావరణ సేవ -Name[tg]=Национальная служба погоды NOAA Name[th]=บริการพยากรณ์อากาศสากล NOAA Name[tr]=NOAA Ulusal Hava Durumu Servisi Name[ug]=NOAA تەمىنلىگەن دۆلەت ھاۋارايى مۇلازىمىتى @@ -105,7 +104,7 @@ Comment[km]=ទិន្នន័យ XML ពី​សេវា​អាកាសធាតុ​ជាតិ​របស់ NOAA Comment[kn]=NOAA ದ ರಾಷ್ಟ್ರೀಯ ಹವಾಮಾನ ಸೇವೆಯಿಂದ XML ದತ್ತ Comment[ko]=NOAA 미국 기상 서비스의 XML 데이터 -Comment[lt]=XML duomenys iš NOAA's National Weather Service +Comment[lt]=XML duomenys iš NOAA nacionalinės orų tarnybos Comment[lv]=XML dati no NOAA Nacionālā Laikapstākļu Servisa (ASV) Comment[mk]=XML-податоци од Националниот сервис за време на NOAA Comment[ml]=നോആ-യുടെ ദേശീയി കാലവസ്ഥാ സേവനത്തില്‍ നിന്നുമുള്ള എക്സ്എംഎല്‍ ഡേറ്റാ @@ -132,7 +131,6 @@ Comment[sv]=XML-data från NOAA:s nationella vädertjänst Comment[ta]=XML Data from NOAA's National Weather Service Comment[te]=NOAA యొక్క జాతీయ వాతావరణ సేవనుండి XML డాటా -Comment[tg]=Маълумоти XML аз NOAA's National Weather Service Comment[th]=ข้อมูล XML จากบริการพยากรณ์อากาศสากล NOAA Comment[tr]=NOAA Ulusal Hava Durumu Servisi'nden XML Verisi Comment[ug]=NOAA دۆلەت ھاۋارايى مۇلازىمىتى تەمىنلىگەن XML سانلىق-مەلۇماتى diff --git a/dataengines/weather/ions/noaa/ion_noaa.h b/dataengines/weather/ions/noaa/ion_noaa.h --- a/dataengines/weather/ions/noaa/ion_noaa.h +++ b/dataengines/weather/ions/noaa/ion_noaa.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2007-2009 by Shawn Starr * + * Copyright (C) 2007-2009,2019 by Shawn Starr * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -42,7 +42,6 @@ public: WeatherData(); - //QString countryName; // USA QString locationName; QString stationID; double stationLatitude; diff --git a/dataengines/weather/ions/noaa/ion_noaa.cpp b/dataengines/weather/ions/noaa/ion_noaa.cpp --- a/dataengines/weather/ions/noaa/ion_noaa.cpp +++ b/dataengines/weather/ions/noaa/ion_noaa.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2007-2009 by Shawn Starr * + * Copyright (C) 2007-2009,2019 by Shawn Starr * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -317,7 +317,7 @@ info.stationID = stationID; info.XMLurl = xmlurl; - QString tmp = stationName + QStringLiteral(", ") + state; // Build the key name. + QString tmp = stationName + QLatin1String(", ") + state; // Build the key name. m_places[tmp] = info; } break; @@ -553,7 +553,6 @@ Plasma::DataEngine::Data data; - data.insert(QStringLiteral("Country"), QStringLiteral("USA")); data.insert(QStringLiteral("Place"), weatherData.locationName); data.insert(QStringLiteral("Station"), weatherData.stationID); @@ -678,86 +677,121 @@ { IonInterface::ConditionIcons result; // Consider any type of storm, tornado or funnel to be a thunderstorm. - if (weather.contains(QStringLiteral("thunderstorm")) || weather.contains(QStringLiteral("funnel")) || - weather.contains(QStringLiteral("tornado")) || weather.contains(QStringLiteral("storm")) || weather.contains(QStringLiteral("tstms"))) { + if (weather.contains(QLatin1String("thunderstorm")) || weather.contains(QLatin1String("funnel")) || + weather.contains(QLatin1String("tornado")) || weather.contains(QLatin1String("storm")) || weather.contains(QLatin1String("tstms"))) { - if (weather.contains(QStringLiteral("vicinity")) || weather.contains(QStringLiteral("chance"))) { + if (weather.contains(QLatin1String("vicinity")) || weather.contains(QLatin1String("chance"))) { result = isDayTime ? IonInterface::ChanceThunderstormDay : IonInterface::ChanceThunderstormNight; } else { result = IonInterface::Thunderstorm; } - } else if (weather.contains(QStringLiteral("pellets")) || weather.contains(QStringLiteral("crystals")) || - weather.contains(QStringLiteral("hail"))) { + } else if (weather.contains(QLatin1String("pellets")) || weather.contains(QLatin1String("crystals")) || + weather.contains(QLatin1String("hail"))) { result = IonInterface::Hail; - } else if (((weather.contains(QStringLiteral("rain")) || weather.contains(QStringLiteral("drizzle")) || - weather.contains(QStringLiteral("showers"))) && weather.contains(QStringLiteral("snow"))) || weather.contains(QStringLiteral("wintry mix"))) { + } else if (((weather.contains(QLatin1String("rain")) || weather.contains(QLatin1String("drizzle")) || + weather.contains(QLatin1String("showers"))) && weather.contains(QLatin1String("snow"))) || weather.contains(QLatin1String("wintry mix"))) { result = IonInterface::RainSnow; - } else if (weather.contains(QStringLiteral("flurries"))) { + } else if (weather.contains(QLatin1String("flurries"))) { result = IonInterface::Flurries; - } else if (weather.contains(QStringLiteral("snow")) && weather.contains(QStringLiteral("light"))) { + } else if (weather.contains(QLatin1String("snow")) && weather.contains(QLatin1String("light"))) { result = IonInterface::LightSnow; - } else if (weather.contains(QStringLiteral("snow"))) { - if (weather.contains(QStringLiteral("vicinity")) || weather.contains(QStringLiteral("chance"))) { + } else if (weather.contains(QLatin1String("snow"))) { + if (weather.contains(QLatin1String("vicinity")) || weather.contains(QLatin1String("chance"))) { result = isDayTime ? IonInterface::ChanceSnowDay : IonInterface::ChanceSnowNight; } else { result = IonInterface::Snow; } - } else if (weather.contains(QStringLiteral("freezing rain"))) { + } else if (weather.contains(QLatin1String("freezing rain"))) { result = IonInterface::FreezingRain; - } else if (weather.contains(QStringLiteral("freezing drizzle"))) { + } else if (weather.contains(QLatin1String("freezing drizzle"))) { result = IonInterface::FreezingDrizzle; - } else if (weather.contains(QStringLiteral("cold"))) { + } else if (weather.contains(QLatin1String("cold"))) { // temperature condition has not hint about air ingredients, so let's assume chance of snow result = isDayTime ? IonInterface::ChanceSnowDay : IonInterface::ChanceSnowNight; - } else if (weather.contains(QStringLiteral("showers"))) { + } else if (weather.contains(QLatin1String("showers"))) { - if (weather.contains(QStringLiteral("vicinity")) || weather.contains(QStringLiteral("chance"))) { + if (weather.contains(QLatin1String("vicinity")) || weather.contains(QLatin1String("chance"))) { result = isDayTime ? IonInterface::ChanceShowersDay : IonInterface::ChanceShowersNight; } else { result = IonInterface::Showers; } - } else if (weather.contains(QStringLiteral("light rain")) || weather.contains(QStringLiteral("drizzle"))) { + } else if (weather.contains(QLatin1String("light rain")) || weather.contains(QLatin1String("drizzle"))) { result = IonInterface::LightRain; - } else if (weather.contains(QStringLiteral("rain"))) { + } else if (weather.contains(QLatin1String("rain"))) { result = IonInterface::Rain; - } else if (weather.contains(QStringLiteral("few clouds")) || weather.contains(QStringLiteral("mostly sunny")) || - weather.contains(QStringLiteral("mostly clear")) || weather.contains(QStringLiteral("increasing clouds")) || - weather.contains(QStringLiteral("becoming cloudy")) || weather.contains(QStringLiteral("clearing")) || - weather.contains(QStringLiteral("decreasing clouds")) || weather.contains(QStringLiteral("becoming sunny"))) { - result = isDayTime ? IonInterface::FewCloudsDay : IonInterface::FewCloudsNight; + } else if (weather.contains(QLatin1String("few clouds")) || weather.contains(QLatin1String("mostly sunny")) || + weather.contains(QLatin1String("mostly clear")) || weather.contains(QLatin1String("increasing clouds")) || + weather.contains(QLatin1String("becoming cloudy")) || weather.contains(QLatin1String("clearing")) || + weather.contains(QLatin1String("decreasing clouds")) || weather.contains(QLatin1String("becoming sunny"))) { + if (weather.contains(QLatin1String("breezy")) || + weather.contains(QLatin1String("wind")) || + weather.contains (QLatin1String("gust"))) { + result = isDayTime ? IonInterface::FewCloudsWindyDay : IonInterface::FewCloudsWindyNight; + } else { + result = isDayTime ? IonInterface::FewCloudsDay : IonInterface::FewCloudsNight; + } - } else if (weather.contains(QStringLiteral("partly cloudy")) || weather.contains(QStringLiteral("partly sunny")) || - weather.contains(QStringLiteral("partly clear"))) { - result = isDayTime ? IonInterface::PartlyCloudyDay : IonInterface::PartlyCloudyNight; + } else if (weather.contains(QLatin1String("partly cloudy")) || weather.contains(QLatin1String("partly sunny")) || + weather.contains(QLatin1String("partly clear"))) { + if (weather.contains(QLatin1String("breezy")) || + weather.contains(QLatin1String("wind")) || + weather.contains (QLatin1String("gust"))) { + result = isDayTime ? IonInterface::PartlyCloudyWindyDay : IonInterface::PartlyCloudyWindyNight; + } else { + result = isDayTime ? IonInterface::PartlyCloudyDay : IonInterface::PartlyCloudyNight; + } - } else if (weather.contains(QStringLiteral("overcast")) || weather.contains(QStringLiteral("cloudy"))) { - result = IonInterface::Overcast; + } else if (weather.contains(QLatin1String("overcast")) || weather.contains(QLatin1String("cloudy"))) { + if (weather.contains(QLatin1String("breezy")) || + weather.contains(QLatin1String("wind")) || + weather.contains (QLatin1String("gust"))) { + result = IonInterface::OvercastWindy; + } else { + result = IonInterface::Overcast; + } - } else if (weather.contains(QStringLiteral("haze")) || weather.contains(QStringLiteral("smoke")) || - weather.contains(QStringLiteral("dust")) || weather.contains(QStringLiteral("sand"))) { + } else if (weather.contains(QLatin1String("haze")) || weather.contains(QLatin1String("smoke")) || + weather.contains(QLatin1String("dust")) || weather.contains(QLatin1String("sand"))) { result = IonInterface::Haze; - } else if (weather.contains(QStringLiteral("fair")) || weather.contains(QStringLiteral("clear")) || weather.contains(QStringLiteral("sunny"))) { - result = isDayTime ? IonInterface::ClearDay : IonInterface::ClearNight; + } else if (weather.contains(QLatin1String("fair")) || weather.contains(QLatin1String("clear")) || weather.contains(QLatin1String("sunny"))) { + if (weather.contains(QLatin1String("breezy")) || + weather.contains(QLatin1String("wind")) || + weather.contains (QLatin1String("gust"))) { + result = isDayTime ? IonInterface::ClearWindyDay : IonInterface::ClearWindyNight; + } else { + result = isDayTime ? IonInterface::ClearDay : IonInterface::ClearNight; + } - } else if (weather.contains(QStringLiteral("fog"))) { + } else if (weather.contains(QLatin1String("fog"))) { result = IonInterface::Mist; - } else if (weather.contains(QStringLiteral("hot"))) { + } else if (weather.contains(QLatin1String("hot"))) { // temperature condition has not hint about air ingredients, so let's assume the sky is clear when it is hot - result = isDayTime ? IonInterface::ClearDay : IonInterface::ClearNight; + if (weather.contains(QLatin1String("breezy")) || + weather.contains(QLatin1String("wind")) || + weather.contains (QLatin1String("gust"))) { + result = isDayTime ? IonInterface::ClearWindyDay : IonInterface::ClearWindyNight; + } else { + result = isDayTime ? IonInterface::ClearDay : IonInterface::ClearNight; + } + } else if (weather.contains (QLatin1String("breezy")) || + weather.contains (QLatin1String("wind")) || + weather.contains (QLatin1String("gust"))) { + // Assume a clear sky when it's windy but no clouds have been mentioned + result = isDayTime ? IonInterface::ClearWindyDay : IonInterface::ClearWindyNight; } else { result = IonInterface::NotAvailable; } diff --git a/dataengines/weather/ions/wetter.com/ion-wettercom.desktop b/dataengines/weather/ions/wetter.com/ion-wettercom.desktop --- a/dataengines/weather/ions/wetter.com/ion-wettercom.desktop +++ b/dataengines/weather/ions/wetter.com/ion-wettercom.desktop @@ -1,5 +1,6 @@ [Desktop Entry] Name=wetter.com +Name[ast]=wetter.com Name[bg]=wetter.com Name[bn]=wetter.com Name[bs]=wetter.com @@ -121,7 +122,6 @@ Comment[sr@ijekavianlatin]=Prognoza vremena sa wetter.com Comment[sr@latin]=Prognoza vremena sa wetter.com Comment[sv]=Väderprognos av wetter.com -Comment[tg]=Обу ҳаво аз wetter.com Comment[th]=พยากรณ์อากาศ โดย wetter.com Comment[tr]=wetter.com'dan hava tahmini Comment[ug]=wetter.com تەمىنلىگەن ھاۋارايىدىن ئالدىن مەلۇمات diff --git a/dataengines/weather/ions/wetter.com/ion_wettercom.cpp b/dataengines/weather/ions/wetter.com/ion_wettercom.cpp --- a/dataengines/weather/ions/wetter.com/ion_wettercom.cpp +++ b/dataengines/weather/ions/wetter.com/ion_wettercom.cpp @@ -629,7 +629,7 @@ } else if (elementName == QLatin1String("tn")) { tempMin = qRound(xml.readElementText().toDouble()); qCDebug(IONENGINE_WETTERCOM) << "parsed t_min:" << tempMin; - } else if (elementName == QLatin1String("w")) { + } else if (elementName == QLatin1Char('w')) { int tmp = xml.readElementText().toInt(); if (!time.isEmpty()) @@ -656,7 +656,7 @@ } else if (elementName == QLatin1String("link")) { weatherData.creditsUrl = xml.readElementText(); qCDebug(IONENGINE_WETTERCOM) << "parsed credits url:" << weatherData.creditsUrl; - } else if (elementName == QLatin1String("d")) { + } else if (elementName == QLatin1Char('d')) { localTime = xml.readElementText().toInt(); qCDebug(IONENGINE_WETTERCOM) << "parsed local time:" << localTime; } else if (elementName == QLatin1String("du")) { diff --git a/dataengines/weather/plasma-dataengine-weather.desktop b/dataengines/weather/plasma-dataengine-weather.desktop --- a/dataengines/weather/plasma-dataengine-weather.desktop +++ b/dataengines/weather/plasma-dataengine-weather.desktop @@ -93,21 +93,21 @@ Comment[fr]=Données météorologiques depuis des sources multiples en ligne Comment[fy]=Waar gegevens fan ferskate ynternetboarnen Comment[ga]=Sonraí aimsire ó fhoinsí éagsúla ar líne -Comment[gl]=Información meteorolóxica de varias fontes en internet +Comment[gl]=Información meteorolóxica de varias fontes en Internet Comment[he]=מידע על מזג האוויר ממגוון מקורות מקוונים Comment[hi]=मोसम जानकारी अनेक ऑनलाईन स्रोतों से Comment[hr]=Podaci o vremenu iz raznih online izvora Comment[hu]=Időjárás-jelentés több forrásból Comment[ia]=Datos meteorologic ex multiple fontes in linea -Comment[id]=Data cuaca dari berbagai sumber daring +Comment[id]=Data cuaca dari berbagai sumber online Comment[is]=Veðurgögn frá ýmsum gagnabönkum á netinu Comment[it]=Dati meteorologici da varie fonti in rete Comment[ja]=複数のオンライン情報源からの気象データ Comment[kk]=Түрлі онлайн көздерден алынған ауа-райы Comment[km]=ទិន្នន័យ​អាកាសធាតុ​ពី​ប្រភព​លើបណ្ដាញ​ជា​ច្រើន Comment[kn]=ಅನೇಕ ಆನ್‌ಲೈನ್ ಮೂಲಗಳಿಂದ ಹವಾಮಾನ ಮಾಹಿತಿ Comment[ko]=온라인에서 온 다양한 날씨 데이터 -Comment[lt]=Orų informacija iš įvairių žiniatinklio šaltinių +Comment[lt]=Orų duomenys iš įvairių internetinių šaltinių Comment[lv]=Laikapstākļu dati no vairākiem tiešsaistes avotiem Comment[mk]=Метеоролошки податоци од разни интернет-извори Comment[ml]=പല സ്രോതസ്സുകളില്‍നിന്നുമുള്ള കാലാവസ്ഥാ സ്ഥിതിവിവരങ്ങള്‍ @@ -130,7 +130,6 @@ Comment[sr@ijekavianlatin]=Meteorološki podaci iz više izvora na vezi Comment[sr@latin]=Meteorološki podaci iz više izvora na vezi Comment[sv]=Väderdata från flera olika nättjänster -Comment[tg]=Иттилооти обу ҳаво аз манбаъҳои гуногун Comment[th]=ข้อมูลพยากรณ์อากาศจากแหล่งข้อมูลออนไลน์ต่าง ๆ Comment[tr]=Birden fazla çevrim içi kaynaktan hava durumu verisi Comment[ug]=توردىكى كۆپ مەنبەدىن كەلگەن ھاۋارايى سانلىق-مەلۇماتى @@ -148,7 +147,7 @@ X-KDE-PluginInfo-Email=shawn.starr@rogers.com X-KDE-PluginInfo-Name=weather X-KDE-PluginInfo-Version=1.0 -X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-Website=https://www.kde.org X-KDE-PluginInfo-License=GPLv2+ X-KDE-PluginInfo-Category= X-KDE-PluginInfo-Depends= diff --git a/dataengines/weather/weatherengine.cpp b/dataengines/weather/weatherengine.cpp --- a/dataengines/weather/weatherengine.cpp +++ b/dataengines/weather/weatherengine.cpp @@ -59,7 +59,7 @@ void WeatherEngine::updateIonList(const QStringList &changedResources) { - if (changedResources.isEmpty() || changedResources.contains(QStringLiteral("services"))) { + if (changedResources.isEmpty() || changedResources.contains(QLatin1String("services"))) { removeAllData(QStringLiteral("ions")); const auto infos = Plasma::PluginLoader::self()->listEngineInfo(QStringLiteral("weatherengine")); for (const KPluginInfo& info : infos) { @@ -205,7 +205,7 @@ qCDebug(WEATHER) << "immediate update of" << source; container->forceImmediateUpdate(); } else { - qCWarning(WEATHER) << "innexplicable failure of" << source; + qCWarning(WEATHER) << "inexplicable failure of" << source; } } diff --git a/doc/kcontrol/translations/go-top.png b/doc/kcontrol/translations/go-top.png new file mode 100644 index 0000000000000000000000000000000000000000..1fc84f067807f8fb553b5c086d51bc0e9739379c GIT binary patch literal 418 zc%17D@N?(olHy`uVBq!ia0vp^Vj#@I3?$8F6>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5ln@Q@332`Z|G%=D=82Oh z%`B}?pE-N%_=%jn0%McIAf1d!-tI089jvk*Kn`btM`SSr1K&#!X5=$=k^u^`mw5WR zvOnhF<2Z zp@UmEtjW`Yo#CsKio+`B1y(mi4phw)+VCRP;YBOwfh7y86!W|kPKBRfV31qKBW|h6 z{~KtwYKdz^NlIc#s#S7PDv)9@GBC8%HL%b%G!8Maure{UGBnXPFt9Q(@KczOj-nwq zKP5A*61N8XT8-O44U!-Y!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NC6cwc)I$z JtaD0e0sslMcpm@& literal 0 Hc$@!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8Uxs6XN>+|9@pQ&38); z`hg;hN#5=*3>~bp9zYIffk$L90|Vbn5N6~vc9H=KvX^-Jy0Sm!;N{Q|_iNU21qumz zx;Tb#Tu)9=5Hk4Ro}k3Y5Xr~H%2L?c4wO_aag8WRNi0dVN-jzTQVd20hL*Yp7P^MU zAqEy!CI(hU#@Yr3Rt5&IP8~8q(U6;;l9^VCTf^+>RUJSLk{}Ji`DrEPiAAXl<>lpi gnR(g8$%zH2dih1^v)|cB0TnTLy85}Sb4q9e0MX%B+5i9m literal 0 Hc$@setSingleShot(true); m_writeGtk2SettingsTimer->setInterval(1000); connect(m_writeGtk2SettingsTimer, &QTimer::timeout, this, &MenuProxy::writeGtk2Settings); @@ -178,9 +178,15 @@ void MenuProxy::writeGtk2Settings() { + QFile rcFile(gtkRc2Path()); + if (!rcFile.exists()) { + // Don't create it here, that would break writing default GTK-2.0 settings on first login, + // as the gtkbreeze kconf_update script only does so if it does not exist + return; + } + qCDebug(DBUSMENUPROXY) << "Writing gtkrc-2.0 to" << (m_enabled ? "enable" : "disable") << "global menu support"; - QFile rcFile(gtkRc2Path()); if (!rcFile.open(QIODevice::ReadWrite | QIODevice::Text)) { return; } diff --git a/gmenu-dbusmenu-proxy/window.cpp b/gmenu-dbusmenu-proxy/window.cpp --- a/gmenu-dbusmenu-proxy/window.cpp +++ b/gmenu-dbusmenu-proxy/window.cpp @@ -564,11 +564,11 @@ result.insert(QStringLiteral("label"), source.value(QStringLiteral("label")).toString()); - if (source.contains(QStringLiteral(":section"))) { + if (source.contains(QLatin1String(":section"))) { result.insert(QStringLiteral("type"), QStringLiteral("separator")); } - const bool isMenu = source.contains(QStringLiteral(":submenu")); + const bool isMenu = source.contains(QLatin1String(":submenu")); if (isMenu) { result.insert(QStringLiteral("children-display"), QStringLiteral("submenu")); } diff --git a/kcms/CMakeLists.txt b/kcms/CMakeLists.txt --- a/kcms/CMakeLists.txt +++ b/kcms/CMakeLists.txt @@ -1 +1,5 @@ add_subdirectory(translations) + +if(KUserFeedback_FOUND) + add_subdirectory(feedback) +endif() diff --git a/kcms/feedback/CMakeLists.txt b/kcms/feedback/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/kcms/feedback/CMakeLists.txt @@ -0,0 +1,16 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kcm_feedback\") + +add_library(kcm_feedback MODULE feedback.cpp) + +target_link_libraries(kcm_feedback + KF5::I18n + KF5::KCMUtils + KF5::QuickAddons + KUserFeedbackCore +) + +kcoreaddons_desktop_to_json(kcm_feedback "kcm_feedback.desktop") + +install(TARGETS kcm_feedback DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms) +install(FILES kcm_feedback.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) +kpackage_install_package(package kcm_feedback kcms) diff --git a/kcms/feedback/Messages.sh b/kcms/feedback/Messages.sh new file mode 100644 --- /dev/null +++ b/kcms/feedback/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.cpp -o -name \*.qml` -o $podir/kcm_feedback.pot diff --git a/kcms/feedback/feedback.h b/kcms/feedback/feedback.h new file mode 100644 --- /dev/null +++ b/kcms/feedback/feedback.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 David Edmundson + * Copyright (C) 2019 Aleix Pol Gonzalez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +#include + +class Feedback : public KQuickAddons::ConfigModule +{ + Q_OBJECT + + Q_PROPERTY(bool feedbackEnabled READ feedbackEnabled CONSTANT) + Q_PROPERTY(int plasmaFeedbackLevel READ plasmaFeedbackLevel WRITE setPlasmaFeedbackLevel NOTIFY plasmaFeedbackLevelChanged) + + public: + explicit Feedback(QObject* parent = nullptr, const QVariantList &list = QVariantList()); + ~Feedback() override; + + bool feedbackEnabled() const; + int plasmaFeedbackLevel() const { return m_plasmaFeedbackLevel; } + + void setPlasmaFeedbackLevel(int plasmaFeedbackLevel) { + if (plasmaFeedbackLevel != m_plasmaFeedbackLevel) { + m_plasmaFeedbackLevel = plasmaFeedbackLevel; + Q_EMIT plasmaFeedbackLevelChanged(plasmaFeedbackLevel); + } + } + + public Q_SLOTS: + void load() override; + void save() override; + void defaults() override; + + Q_SIGNALS: + void plasmaFeedbackLevelChanged(bool plasmaFeedbackLevel); + + private: + KSharedConfig::Ptr m_plasmaConfig; + int m_plasmaFeedbackLevel = 0; +}; diff --git a/kcms/feedback/feedback.cpp b/kcms/feedback/feedback.cpp new file mode 100644 --- /dev/null +++ b/kcms/feedback/feedback.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 David Edmundson + * Copyright (C) 2019 Aleix Pol Gonzalez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "feedback.h" + +#include +#include +#include +#include +#include + +#include + +K_PLUGIN_CLASS_WITH_JSON(Feedback, "kcm_feedback.json"); + +Feedback::Feedback(QObject *parent, const QVariantList &args) + : KQuickAddons::ConfigModule(parent) + //UserFeedback.conf is used by KUserFeedback which uses QSettings and won't go through globals + , m_plasmaConfig(KSharedConfig::openConfig(QStringLiteral("PlasmaUserFeedback"))) +{ + Q_UNUSED(args) + setAboutData(new KAboutData(QStringLiteral("kcm_feedback"), + i18n("User Feedback"), + QStringLiteral("1.0"), i18n("Configure user feedback settings"), KAboutLicense::LGPL)); + + connect(this, &Feedback::plasmaFeedbackLevelChanged, this, [this](){ + setNeedsSave(true); + }); +} + +Feedback::~Feedback() = default; + +bool Feedback::feedbackEnabled() const +{ + KUserFeedback::Provider p; + return p.isEnabled(); +} + +void Feedback::load() +{ + //We only operate if the kill switch is off, all KDE components should default to KUserFeedback::Provider::NoTelemetry + setPlasmaFeedbackLevel(m_plasmaConfig->group("Global").readEntry("FeedbackLevel", int(KUserFeedback::Provider::NoTelemetry))); + setNeedsSave(false); +} + +void Feedback::save() +{ + m_plasmaConfig->group("Global").writeEntry("FeedbackLevel", m_plasmaFeedbackLevel); + m_plasmaConfig->sync(); +} + +void Feedback::defaults() +{ + setPlasmaFeedbackLevel(KUserFeedback::Provider::NoTelemetry); +} + +#include "feedback.moc" diff --git a/kcms/feedback/kcm_feedback.desktop b/kcms/feedback/kcm_feedback.desktop new file mode 100644 --- /dev/null +++ b/kcms/feedback/kcm_feedback.desktop @@ -0,0 +1,67 @@ +[Desktop Entry] +Exec=kcmshell5 feedback +Icon=preferences-desktop-locale +Type=Service +X-KDE-ServiceTypes=KCModule +X-DocPath=kcontrol/feedback/index.html + +X-KDE-Library=kcm_feedback +X-KDE-ParentApp=kcontrol + +X-KDE-System-Settings-Parent-Category=personalization + +Name=User Feedback +Name[ca]=Comentaris de l'usuari +Name[cs]=Zpětná vazba uživatele +Name[en_GB]=User Feedback +Name[es]=Comentarios del usuario +Name[et]=Kasutaja tagasiside +Name[eu]=Erabiltzaileen berrelikadura +Name[fi]=Käyttäjäpalaute +Name[fr]=Retours des utilisateurs +Name[gl]=Achegas de usuario +Name[hu]=Felhasználói visszajelzés +Name[id]=Tanggapan Pengguna +Name[it]=Segnalazioni dell'utente +Name[lt]=Naudotojo grįžtamasis ryšys +Name[nl]=Terugkoppeling van gebruiker +Name[nn]=Tilbakemeldingar +Name[pl]=Informacja zwrotna +Name[pt]=Reacções do Utilizador +Name[pt_BR]=Comentários dos usuários +Name[ru]=Обратная связь +Name[sk]=Používateľská odozva +Name[sv]=Användaråterkoppling +Name[tg]=Изҳори назари корбар +Name[uk]=Відгуки користувача +Name[x-test]=xxUser Feedbackxx +Name[zh_CN]=用户反馈 +Name[zh_TW]=使用者意見回應 +Comment=Configure user feedback settings +Comment[ca]=Configura els ajustaments dels comentaris de l'usuari +Comment[cs]=Nastavení voleb uživatelské zpětné vazby +Comment[en_GB]=Configure user feedback settings +Comment[es]=Configurar las preferencias de los comentarios del usuario +Comment[et]=Kasutaja tagasiside seadistamine +Comment[eu]=Konfiguratu erabiltzaileen berrelikadura ezarpenak +Comment[fi]=Käyttäjäpalautteen asetukset +Comment[fr]=Configurer les paramètres des retours des utilisateurs +Comment[gl]=Configurar as achegas de usuario +Comment[hu]=A felhasználói visszajelzések beállításai +Comment[id]=Konfigurasikan pengaturan tanggapan pengguna +Comment[it]=Configura le impostazioni di segnalazione dell'utente +Comment[lt]=Konfigūruoti naudotojo grįžtamojo ryšio nuostatas +Comment[nl]=Instellingen voor terugkoppeling van gebruikers configureren +Comment[nn]=Set opp tilbakemeldingar frå brukarar +Comment[pl]=Ustawienia informacji zwrotnej +Comment[pt]=Configurar as opções das reacções do utilizador +Comment[pt_BR]=Configure as definições dos comentários dos usuários +Comment[ru]=Настройка параметров обратной связи +Comment[sk]=Nastaviť používateľskú odozvu +Comment[sv]=Anpassa inställningar av användaråterkoppling +Comment[uk]=Налаштовування системи відгуків користувача +Comment[x-test]=xxConfigure user feedback settingsxx +Comment[zh_CN]=配置用户反馈设置 +Comment[zh_TW]=設定使用者意見回應設定 + +Categories=Qt;KDE; diff --git a/kcms/feedback/package/contents/ui/main.qml b/kcms/feedback/package/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/kcms/feedback/package/contents/ui/main.qml @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2019 David Edmundson + * Copyright (C) 2019 Aleix Pol Gonzalez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 as QQC2 +import org.kde.kirigami 2.6 as Kirigami +import org.kde.userfeedback 1.0 as UserFeedback +import org.kde.kcm 1.2 + +SimpleKCM { + id: root + + ConfigModule.buttons: ConfigModule.Defaults | ConfigModule.Apply + leftPadding: width * 0.1 + rightPadding: leftPadding + + + ColumnLayout { + Kirigami.InlineMessage { + id: infoLabel + Layout.fillWidth: true + + type: Kirigami.MessageType.Information + visible: !form.enabled + text: i18n("User Feedback has been disabled centrally. Please contact your distributor.") + } + + QQC2.Label { + Kirigami.FormData.label: i18n("Plasma:") + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: xi18nc("@info", "You can read about our policy in the following link:") + } + + Kirigami.UrlButton { + Layout.alignment: Qt.AlignHCenter + url: "https://kde.org/privacypolicy-apps.php" + } + + Kirigami.Separator { + Layout.fillWidth: true + Layout.topMargin: Kirigami.Units.gridUnit + Layout.bottomMargin: Kirigami.Units.gridUnit + } + + Kirigami.FormLayout { + id: form + enabled: kcm.feedbackEnabled + QQC2.Slider { + id: statisticsModeSlider + Kirigami.FormData.label: i18n("Plasma:") + enabled: kcm.feedbackEnabled + Layout.fillWidth: true + + readonly property var modeOptions: [UserFeedback.Provider.NoTelemetry, UserFeedback.Provider.BasicSystemInformation, UserFeedback.Provider.BasicUsageStatistics, + UserFeedback.Provider.DetailedSystemInformation, UserFeedback.Provider.DetailedUsageStatistics] + from: 0 + to: modeOptions.length - 1 + stepSize: 1 + snapMode: QQC2.Slider.SnapAlways + + function findIndex(array, what) { + for (var v in array) { + if (array[v] == what) + return v; + } + return null; + } + + Component.onCompleted: { + var idx = findIndex(modeOptions, kcm.plasmaFeedbackLevel) + value = idx===null ? 2 : modeOptions[idx] + } + + onMoved: { + kcm.plasmaFeedbackLevel = modeOptions[value] + } + } + + UserFeedback.FeedbackConfigUiController { + id: feedbackController + applicationName: i18n("Plasma") + } + + Kirigami.Heading { + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: root.width * 0.5 + wrapMode: Text.WordWrap + level: 3 + text: feedbackController.telemetryName(statisticsModeSlider.modeOptions[statisticsModeSlider.value]) + } + QQC2.Label { + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: root.width * 0.5 + wrapMode: Text.WordWrap + enabled: statisticsModeSlider.value > 0 + + text: { + feedbackController.applicationName + return feedbackController.telemetryDescription(statisticsModeSlider.modeOptions[statisticsModeSlider.value]) + } + } + } + } +} + diff --git a/kcms/feedback/package/metadata.desktop b/kcms/feedback/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/kcms/feedback/package/metadata.desktop @@ -0,0 +1,39 @@ +[Desktop Entry] +Name=Feedback +Name[ca]=Comentaris +Name[cs]=Zpětná vazba +Name[de]=Rückmeldung +Name[en_GB]=Feedback +Name[es]=Comentarios +Name[et]=Tagasiside +Name[eu]=Berrelikadura +Name[fi]=Palaute +Name[fr]=Retours +Name[gl]=Achegas +Name[hu]=Visszajelzés +Name[id]=Tanggapan +Name[it]=Segnalazioni +Name[lt]=Grįžtamasis ryšys +Name[lv]=Atsauksmes +Name[nl]=Terugkoppeling +Name[nn]=Tilbakemelding +Name[pl]=Informacja zwrotna +Name[pt]=Reacções +Name[pt_BR]=Comentários +Name[ru]=Обратная связь +Name[sk]=Odozva +Name[sv]=Återkoppling +Name[tg]=Изҳори назар +Name[uk]=Відгуки +Name[x-test]=xxFeedbackxx +Name[zh_CN]=反馈 +Name[zh_TW]=意見回應 + +Icon=preferences-desktop-locale +Type=Service +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=kcm_feedback +X-KDE-ServiceTypes=Plasma/Generic +X-Plasma-API=declarativeappletscript + +X-Plasma-MainScript=ui/main.qml diff --git a/kcms/translations/CMakeLists.txt b/kcms/translations/CMakeLists.txt --- a/kcms/translations/CMakeLists.txt +++ b/kcms/translations/CMakeLists.txt @@ -6,7 +6,8 @@ ########### next target ############### -set(kcm_translations_PART_SRCS translations.cpp translationsmodel.cpp) +set(kcm_translations_PART_SRCS translations.cpp translationsmodel.cpp translationssettings.cpp) +kconfig_add_kcfg_files(kcm_translations_PART_SRCS translationssettingsbase.kcfgc GENERATE_MOC) add_library(kcm_translations MODULE ${kcm_translations_PART_SRCS}) diff --git a/kcms/translations/kcm_translations.desktop b/kcms/translations/kcm_translations.desktop --- a/kcms/translations/kcm_translations.desktop +++ b/kcms/translations/kcm_translations.desktop @@ -10,33 +10,69 @@ X-KDE-System-Settings-Parent-Category=regionalsettings X-KDE-Weight=40 +X-KDE-FormFactors=tablet,handset,desktop Name=Language +Name[ast]=Llingua Name[ca]=Idioma Name[ca@valencia]=Idioma Name[cs]=Jazyk Name[da]=Sprog Name[de]=Sprache Name[en_GB]=Language Name[es]=Idioma +Name[et]=Keel Name[eu]=Hizkuntza Name[fi]=Kieli Name[fr]=Langue Name[gl]=Idioma +Name[hu]=Nyelv Name[id]=Bahasa Name[it]=Lingua Name[ko]=언어 +Name[lt]=Kalba +Name[lv]=Valoda Name[nl]=Taal Name[nn]=Språk Name[pl]=Język Name[pt]=Língua Name[pt_BR]=Idioma Name[ru]=Язык Name[sk]=Jazyk Name[sv]=Språk +Name[tg]=Забон Name[uk]=Мова Name[x-test]=xxLanguagexx Name[zh_CN]=语言 Name[zh_TW]=語言 +Comment=Change workspace and application languages +Comment[ast]=Camuda les llingües de la estaya de trabayu y les aplicaciones +Comment[ca]=Canvi dels idiomes de l'espai de treball i les aplicacions +Comment[cs]=Změnit pracovní prostředí a jazyk aplikace +Comment[da]=Skift sprog for skrivebordet og programmer +Comment[de]=Sprache für den Arbeitsbereich und die Anwendungen ändern +Comment[en_GB]=Change workspace and application languages +Comment[es]=Cambiar los idiomas del espacio de trabajo y de las aplicaciones +Comment[et]=Töötsooni ja rakenduste keele muutmine +Comment[eu]=Aldatu languneko eta aplikazioko hizkuntzak +Comment[fi]=Vaihda työtilan ja sovellusten kieltä +Comment[fr]=Changer la langue de l'espace de travail et des applications +Comment[gl]=Cambiar os idiomas do espazo de traballo e das aplicacións +Comment[hu]=A munkaterület és az alkalmazások nyelvének beállításai +Comment[id]=Ubah bahasa aplikasi dan ruangkerja +Comment[it]=Cambia la lingua dello spazio di lavoro e dell'applicazione +Comment[lt]=Keisti darbo srities ir programų kalbas +Comment[nl]=Werkruimte en talen van toepassingen wijzigen +Comment[nn]=Endra språk for arbeidsflate og program +Comment[pl]=Zmień język przestrzeni pracy i aplikacji +Comment[pt]=Mudar as línguas da área de trabalho e da aplicação +Comment[pt_BR]=Altera os idiomas do espaço de trabalho e aplicativos +Comment[ru]=Изменение языка приложений и рабочего среды +Comment[sk]=Zmeniť jazyky pracovného priestoru a aplikácií +Comment[sv]=Ändra arbetsområde och programspråk +Comment[uk]=Зміна мови перекладу робочого простору і програм +Comment[x-test]=xxChange workspace and application languagesxx +Comment[zh_CN]=更改工作空间和应用程序语言 +Comment[zh_TW]=變更工作區及應用程式的語言 Categories=Qt;KDE;X-KDE-settings-translations; diff --git a/kcms/translations/package/contents/ui/main.qml b/kcms/translations/package/contents/ui/main.qml --- a/kcms/translations/package/contents/ui/main.qml +++ b/kcms/translations/package/contents/ui/main.qml @@ -29,7 +29,6 @@ id: root ConfigModule.quickHelp: i18n("Language") - ConfigModule.buttons: ConfigModule.Help | ConfigModule.Defaults | ConfigModule.Apply Component { id: addLanguageItemComponent @@ -134,9 +133,9 @@ Kirigami.InlineMessage { Layout.fillWidth: true - type: Kirigami.MessageType.Information + type: Kirigami.MessageType.Error - text: i18nc("@info", "There are no languages available on this system.") + text: i18nc("@info", "There are no additional languages available on this system.") visible: !availableLanguagesList.count } @@ -161,7 +160,7 @@ "The translation files for the language with the code '%2' could not be found. The language will be removed from your configuration. If you want to add it back, please install the localization files for it and add the language again.", "The translation files for the languages with the codes '%2' could not be found. These languages will be removed from your configuration. If you want to add them back, please install the localization files for it and the languages again.", kcm.selectedTranslationsModel.missingLanguages.length, - kcm.selectedTranslationsModel.missingLanguages.join("', '")) + kcm.selectedTranslationsModel.missingLanguages.join(i18nc("@info separator in list of language codes", "', '"))) visible: kcm.selectedTranslationsModel.missingLanguages.length } @@ -178,71 +177,71 @@ Component { id: languagesListItemComponent - Kirigami.SwipeListItem { - id: listItem + Item { + width: ListView.view.width + height: listItem.implicitHeight - contentItem: RowLayout { - Kirigami.ListItemDragHandle { - listItem: listItem - listView: languagesList - onMoveRequested: kcm.selectedTranslationsModel.move(oldIndex, newIndex) - } + Kirigami.SwipeListItem { + id: listItem - Kirigami.Icon { - visible: model.IsMissing + contentItem: RowLayout { + Kirigami.ListItemDragHandle { + listItem: listItem + listView: languagesList + onMoveRequested: kcm.selectedTranslationsModel.move(oldIndex, newIndex) + } - Layout.alignment: Qt.AlignVCenter + Kirigami.Icon { + visible: model.IsMissing - width: Kirigami.Units.iconSizes.smallMedium - height: width + Layout.alignment: Qt.AlignVCenter - source: "error" - color: Kirigami.Theme.negativeTextColor - } + width: Kirigami.Units.iconSizes.smallMedium + height: width - QtControls.Label { - Layout.fillWidth: true + source: "error" + color: Kirigami.Theme.negativeTextColor + } - Layout.alignment: Qt.AlignVCenter + QtControls.Label { + Layout.fillWidth: true - text: (index == 0) ? i18nc("@item:inlistbox 1 = Language name", "%1 (Default)", model.display) : model.display + Layout.alignment: Qt.AlignVCenter - color: (model.IsMissing ? Kirigami.Theme.negativeTextColor - : (listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) - ? listItem.activeTextColor : listItem.textColor)) - } - } + text: (index == 0) ? i18nc("@item:inlistbox 1 = Language name", "%1 (Default)", model.display) : model.display - actions: [ - Kirigami.Action { - enabled: !model.IsMissing && index > 0 - iconName: "go-top" - tooltip: i18nc("@info:tooltip", "Promote to default") - onTriggered: kcm.selectedTranslationsModel.move(index, 0) - }, - Kirigami.Action { - property bool removing: false - enabled: removing || !model.IsMissing && languagesList.count > 1 - iconName: "list-remove" - tooltip: i18nc("@info:tooltip", "Remove") - onTriggered: { - removing = true; // Don't crash by re-evaluating `enabled` during destruction. - kcm.selectedTranslationsModel.remove(model.LanguageCode); + color: (model.IsMissing ? Kirigami.Theme.negativeTextColor + : (listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) + ? listItem.activeTextColor : listItem.textColor)) + } } - }] + + actions: [ + Kirigami.Action { + enabled: !model.IsMissing && index > 0 + iconName: "go-top" + tooltip: i18nc("@info:tooltip", "Promote to default") + onTriggered: kcm.selectedTranslationsModel.move(index, 0) + }, + Kirigami.Action { + property bool removing: false + enabled: removing || !model.IsMissing && languagesList.count > 1 + iconName: "list-remove" + tooltip: i18nc("@info:tooltip", "Remove") + onTriggered: { + removing = true; // Don't crash by re-evaluating `enabled` during destruction. + kcm.selectedTranslationsModel.remove(model.LanguageCode); + } + }] + } } } view: ListView { id: languagesList model: kcm.selectedTranslationsModel - - delegate: Kirigami.DelegateRecycler { - width: languagesList.width - - sourceComponent: languagesListItemComponent - } + delegate: languagesListItemComponent } footer: RowLayout { diff --git a/kcms/translations/package/metadata.desktop b/kcms/translations/package/metadata.desktop --- a/kcms/translations/package/metadata.desktop +++ b/kcms/translations/package/metadata.desktop @@ -1,27 +1,33 @@ [Desktop Entry] Name=Language +Name[ast]=Llingua Name[ca]=Idioma Name[ca@valencia]=Idioma Name[cs]=Jazyk Name[da]=Sprog Name[de]=Sprache Name[en_GB]=Language Name[es]=Idioma +Name[et]=Keel Name[eu]=Hizkuntza Name[fi]=Kieli Name[fr]=Langue Name[gl]=Idioma +Name[hu]=Nyelv Name[id]=Bahasa Name[it]=Lingua Name[ko]=언어 +Name[lt]=Kalba +Name[lv]=Valoda Name[nl]=Taal Name[nn]=Språk Name[pl]=Język Name[pt]=Língua Name[pt_BR]=Idioma Name[ru]=Язык Name[sk]=Jazyk Name[sv]=Språk +Name[tg]=Забон Name[uk]=Мова Name[x-test]=xxLanguagexx Name[zh_CN]=语言 @@ -33,5 +39,6 @@ X-KDE-PluginInfo-Name=kcm_translations X-KDE-ServiceTypes=Plasma/Generic X-Plasma-API=declarativeappletscript +X-KDE-FormFactors=tablet,handset,desktop X-Plasma-MainScript=ui/main.qml diff --git a/kcms/translations/translations.h b/kcms/translations/translations.h --- a/kcms/translations/translations.h +++ b/kcms/translations/translations.h @@ -1,6 +1,7 @@ /* * Copyright (C) 2014 John Layt * Copyright (C) 2018 Eike Hein + * Copyright (C) 2019 Kevin Ottens * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -21,16 +22,14 @@ #ifndef TRANSLATIONS_H #define TRANSLATIONS_H -#include - -#include +#include class AvailableTranslationsModel; class SelectedTranslationsModel; class TranslationsModel; +class TranslationsSettings; - -class Translations : public KQuickAddons::ConfigModule +class Translations : public KQuickAddons::ManagedConfigModule { Q_OBJECT @@ -59,16 +58,15 @@ private Q_SLOTS: void selectedLanguagesChanged(); - void missingLanguagesChanged(); private: + bool isSaveNeeded() const override; + + TranslationsSettings *m_settings; TranslationsModel *m_translationsModel; SelectedTranslationsModel *m_selectedTranslationsModel; AvailableTranslationsModel *m_availableTranslationsModel; - KConfigGroup m_config; - QStringList m_configuredLanguages; - bool m_everSaved; }; diff --git a/kcms/translations/translations.cpp b/kcms/translations/translations.cpp --- a/kcms/translations/translations.cpp +++ b/kcms/translations/translations.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2014 John Layt * Copyright (C) 2018 Eike Hein + * Copyright (C) 2019 Kevin Ottens * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -20,8 +21,7 @@ #include "translations.h" #include "translationsmodel.h" - -#include "writeexports.h" +#include "translationssettings.h" #include #include @@ -31,7 +31,8 @@ K_PLUGIN_CLASS_WITH_JSON(Translations, "kcm_translations.json") Translations::Translations(QObject *parent, const QVariantList &args) - : KQuickAddons::ConfigModule(parent, args) + : KQuickAddons::ManagedConfigModule(parent, args) + , m_settings(new TranslationsSettings(this)) , m_translationsModel(new TranslationsModel(this)) , m_selectedTranslationsModel(new SelectedTranslationsModel(this)) , m_availableTranslationsModel(new AvailableTranslationsModel(this)) @@ -44,15 +45,10 @@ setButtons(Apply | Default); - m_config = KConfigGroup(KSharedConfig::openConfig(configFile), "Translations"); - connect(m_selectedTranslationsModel, &SelectedTranslationsModel::selectedLanguagesChanged, - this, &Translations::selectedLanguagesChanged); - connect(m_selectedTranslationsModel, &SelectedTranslationsModel::missingLanguagesChanged, - this, &Translations::missingLanguagesChanged); - + this, &Translations::selectedLanguagesChanged); connect(m_selectedTranslationsModel, &SelectedTranslationsModel::selectedLanguagesChanged, - m_availableTranslationsModel, &AvailableTranslationsModel::setSelectedLanguages); + m_availableTranslationsModel, &AvailableTranslationsModel::setSelectedLanguages); } Translations::~Translations() @@ -81,64 +77,41 @@ void Translations::load() { - m_configuredLanguages = m_config.readEntry(lcLanguage, - QString()).split(QLatin1Char(':'), QString::SkipEmptyParts); - - m_availableTranslationsModel->setSelectedLanguages(m_configuredLanguages); - m_selectedTranslationsModel->setSelectedLanguages(m_configuredLanguages); + KQuickAddons::ManagedConfigModule::load(); + m_availableTranslationsModel->setSelectedLanguages(m_settings->configuredLanguages()); + m_selectedTranslationsModel->setSelectedLanguages(m_settings->configuredLanguages()); } void Translations::save() { m_everSaved = true; emit everSavedChanged(); - - m_configuredLanguages = m_selectedTranslationsModel->selectedLanguages(); - - const auto missingLanguages = m_selectedTranslationsModel->missingLanguages(); - for (const QString& lang : missingLanguages) { - m_configuredLanguages.removeOne(lang); - } - - m_config.writeEntry(lcLanguage, m_configuredLanguages.join(QStringLiteral(":")), KConfig::Persistent); - m_config.sync(); - - writeExports(); - - m_selectedTranslationsModel->setSelectedLanguages(m_configuredLanguages); + KQuickAddons::ManagedConfigModule::save(); } void Translations::defaults() { - KConfigGroup formatsConfig = KConfigGroup(KSharedConfig::openConfig(configFile), "Formats"); - - QString lang = formatsConfig.readEntry("LANG", QString()); - - if (lang.isEmpty() - || !KLocalizedString::availableDomainTranslations("systemsettings").contains(lang)) { - lang = QLocale::system().name(); - } - - if (!KLocalizedString::availableDomainTranslations("systemsettings").contains(lang)) { - lang = QStringLiteral("en_US"); - } - - QStringList languages; - languages << lang; - - m_selectedTranslationsModel->setSelectedLanguages(languages); + KQuickAddons::ManagedConfigModule::defaults(); + m_availableTranslationsModel->setSelectedLanguages(m_settings->configuredLanguages()); + m_selectedTranslationsModel->setSelectedLanguages(m_settings->configuredLanguages()); } void Translations::selectedLanguagesChanged() { - setNeedsSave(m_configuredLanguages != m_selectedTranslationsModel->selectedLanguages()); + auto configuredLanguages = m_selectedTranslationsModel->selectedLanguages(); + + const auto missingLanguages = m_selectedTranslationsModel->missingLanguages(); + for (const auto &lang : missingLanguages) { + configuredLanguages.removeOne(lang); + } + + m_settings->setConfiguredLanguages(configuredLanguages); + m_selectedTranslationsModel->setSelectedLanguages(configuredLanguages); } -void Translations::missingLanguagesChanged() +bool Translations::isSaveNeeded() const { - if (!m_selectedTranslationsModel->missingLanguages().isEmpty()) { - setNeedsSave(true); - } + return !m_selectedTranslationsModel->missingLanguages().isEmpty(); } #include "translations.moc" diff --git a/kcms/translations/translationsmodel.cpp b/kcms/translations/translationsmodel.cpp --- a/kcms/translations/translationsmodel.cpp +++ b/kcms/translations/translationsmodel.cpp @@ -34,8 +34,8 @@ : QAbstractListModel(parent) { if (m_installedLanguages.isEmpty()) { - m_installedLanguages = KLocalizedString::availableDomainTranslations("systemsettings"); - m_languages = m_installedLanguages.toList(); + m_installedLanguages = KLocalizedString::availableDomainTranslations("plasmashell"); + m_languages = m_installedLanguages.values(); } } @@ -91,7 +91,7 @@ return languageCode; } - if (languageCode.contains(QStringLiteral("@"))) { + if (languageCode.contains(QLatin1Char('@'))) { return i18nc("%1 is language name, %2 is language code name", "%1 (%2)", languageName, languageCode); } @@ -274,7 +274,7 @@ { beginResetModel(); - m_availableLanguages = (m_installedLanguages - QSet::fromList(languages)).toList(); + m_availableLanguages = (m_installedLanguages - QSet::fromList(languages)).values(); QCollator c; c.setCaseSensitivity(Qt::CaseInsensitive); diff --git a/kcms/translations/translationssettings.h b/kcms/translations/translationssettings.h new file mode 100644 --- /dev/null +++ b/kcms/translations/translationssettings.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 Kevin Ottens + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef TRANSLATIONSSETTINGS_H +#define TRANSLATIONSSETTINGS_H + +#include "translationssettingsbase.h" + +class TranslationsSettings : public TranslationsSettingsBase +{ + Q_OBJECT + Q_PROPERTY(QStringList configuredLanguages READ configuredLanguages WRITE setConfiguredLanguages NOTIFY configuredLanguagesChanged) +public: + TranslationsSettings(QObject *parent = nullptr); + ~TranslationsSettings() override; + + QStringList configuredLanguages() const; + void setConfiguredLanguages(const QStringList &langs); + +signals: + void configuredLanguagesChanged(); +}; + +#endif // TRANSLATIONSSETTINGS_H diff --git a/kcms/translations/translationssettings.cpp b/kcms/translations/translationssettings.cpp new file mode 100644 --- /dev/null +++ b/kcms/translations/translationssettings.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 Kevin Ottens + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "translationssettings.h" + +TranslationsSettings::TranslationsSettings(QObject *parent) + : TranslationsSettingsBase(parent) +{ + connect(this, &TranslationsSettingsBase::languageStringChanged, + this, &TranslationsSettings::configuredLanguagesChanged); +} + +TranslationsSettings::~TranslationsSettings() +{ +} + +QStringList TranslationsSettings::configuredLanguages() const +{ + return languageString().split(QLatin1Char(':'), QString::SkipEmptyParts); +} + +void TranslationsSettings::setConfiguredLanguages(const QStringList &langs) +{ + setLanguageString(langs.join(QLatin1Char(':'))); +} diff --git a/kcms/translations/translationssettingsbase.kcfg b/kcms/translations/translationssettingsbase.kcfg new file mode 100644 --- /dev/null +++ b/kcms/translations/translationssettingsbase.kcfg @@ -0,0 +1,28 @@ + + + + KLocalizedString + + + + KConfigGroup formatsConfig = KConfigGroup(KSharedConfig::openConfig("plasma-localerc"), "Formats"); + + QString lang = formatsConfig.readEntry("LANG", QString()); + + if (lang.isEmpty() + || !KLocalizedString::availableDomainTranslations("plasmashell").contains(lang)) { + lang = QLocale::system().name(); + } + + if (!KLocalizedString::availableDomainTranslations("plasmashell").contains(lang)) { + lang = QStringLiteral("en_US"); + } + + lang + + + + diff --git a/kcms/translations/translationssettingsbase.kcfgc b/kcms/translations/translationssettingsbase.kcfgc new file mode 100644 --- /dev/null +++ b/kcms/translations/translationssettingsbase.kcfgc @@ -0,0 +1,6 @@ +File=translationssettingsbase.kcfg +ClassName=TranslationsSettingsBase +Mutators=true +DefaultValueGetters=true +GenerateProperties=true +ParentInConstructor=true diff --git a/kcms/translations/writeexports.h b/kcms/translations/writeexports.h deleted file mode 100644 --- a/kcms/translations/writeexports.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2014 Sebastian Kügler - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - */ - -#ifndef WRITEEXPORTS_H -#define WRITEEXPORTS_H - -#include -#include -#include - -#include - -const static QString configFile = QStringLiteral("plasma-localerc"); -const static QString exportFile = QStringLiteral("plasma-locale-settings.sh"); - -const static QString lcLang = QStringLiteral("LANG"); - -const static QString lcNumeric = QStringLiteral("LC_NUMERIC"); -const static QString lcTime = QStringLiteral("LC_TIME"); -const static QString lcMonetary = QStringLiteral("LC_MONETARY"); -const static QString lcMeasurement = QStringLiteral("LC_MEASUREMENT"); -const static QString lcCollate = QStringLiteral("LC_COLLATE"); -const static QString lcCtype = QStringLiteral("LC_CTYPE"); - -const static QString lcLanguage = QStringLiteral("LANGUAGE"); - - -void writeExports() -{ - const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + exportFile; - - QString script(QStringLiteral("# Generated script, do not edit\n")); - script.append(QLatin1String("# Exports language-format specific env vars from startkde.\n")); - script.append(QLatin1String("# This script has been generated from kcmshell5 formats.\n")); - script.append(QLatin1String("# It will automatically be overwritten from there.\n")); - KConfigGroup formatsConfig = KConfigGroup(KSharedConfig::openConfig(configFile), "Formats"); - KConfigGroup languageConfig = KConfigGroup(KSharedConfig::openConfig(configFile), "Translations"); - - const QString _export = QStringLiteral("export "); - - // Formats, uses LC_* and LANG variables - const QString lang = formatsConfig.readEntry(lcLang, QString()); - if (!lang.isEmpty()) { - script.append(_export + lcLang + QLatin1Char('=') + lang + QLatin1Char('\n')); - } - - const QString numeric = formatsConfig.readEntry(lcNumeric, QString()); - if (!numeric.isEmpty()) { - script.append(_export + lcNumeric + QLatin1Char('=') + numeric + QLatin1Char('\n')); - } - - const QString time = formatsConfig.readEntry(lcTime, QString()); - if (!time.isEmpty()) { - script.append(_export + lcTime + QLatin1Char('=') + time + QLatin1Char('\n')); - } - - const QString monetary = formatsConfig.readEntry(lcMonetary, QString()); - if (!monetary.isEmpty()) { - script.append(_export + lcMonetary + QLatin1Char('=') + monetary + QLatin1Char('\n')); - } - - const QString measurement = formatsConfig.readEntry(lcMeasurement, QString()); - if (!measurement.isEmpty()) { - script.append(_export + lcMeasurement + QLatin1Char('=') + measurement + QLatin1Char('\n')); - } - - const QString collate = formatsConfig.readEntry(lcCollate, QString()); - if (!collate.isEmpty()) { - script.append(_export + lcCollate + QLatin1Char('=') + collate + QLatin1Char('\n')); - } - - const QString ctype = formatsConfig.readEntry(lcCtype, QString()); - if (!ctype.isEmpty()) { - script.append(_export + lcCtype + QLatin1Char('=') + ctype + QLatin1Char('\n')); - } - - // Translations, uses LANGUAGE variable - const QString language = languageConfig.readEntry(lcLanguage, QString()); - if (!language.isEmpty()) { - script.append(_export + lcLanguage + QLatin1Char('=') + language + QLatin1Char('\n')); - } - - - - QFile file(configPath); - file.open(QIODevice::WriteOnly | QIODevice::Text); - QTextStream out(&file); - - qDebug() << "Wrote script: " << configPath << "\n" << script; - out << script; - file.close(); -} - -#endif diff --git a/kioslave/desktop/desktopnotifier.desktop b/kioslave/desktop/desktopnotifier.desktop --- a/kioslave/desktop/desktopnotifier.desktop +++ b/kioslave/desktop/desktopnotifier.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Type=Service Name=Directory Watcher +Name[ast]=Supervisor de direutorios Name[ca]=Vigilant de directori Name[ca@valencia]=Vigilant de directori Name[cs]=Sledovač adresářů @@ -21,7 +22,8 @@ Name[it]=Sorveglianza delle cartelle Name[ja]=ディレクトリウォッチャ Name[ko]=디렉터리 감시기 -Name[lt]=Aplanko stebėtojas +Name[lt]=Katalogų stebėtojas +Name[lv]=Mapju novērotājs Name[nl]=Mappenbewaker Name[nn]=Mappeovervaking Name[pa]=ਡਾਇਰੈਕਟਰੀ ਨਿਗਰਾਨ @@ -48,6 +50,7 @@ X-KDE-Kded-load-on-demand=true X-KDE-Kded-autoload=false Comment=Monitors directories for changes +Comment[ast]=Supervisa cambeos nos direutorios Comment[ca]=Controla els canvis en els directoris Comment[ca@valencia]=Controla els canvis en els directoris Comment[cs]=Monitoruje změny v adresáři @@ -60,15 +63,15 @@ Comment[eu]=Direktorioetan aldaketak behatzen ditu Comment[fi]=Tarkkailee muutoksia kansioissa Comment[fr]=Surveille les changements d'un dossier -Comment[gl]=Monitoriza os cambios nos directorios. +Comment[gl]=Vixía os cambios nos directorios. Comment[he]=עקוב אחר שינויים בתיקיות Comment[hu]=Mappák változásainak figyelése Comment[id]=Memantau direktori terhadap perubahan Comment[is]=Fylgist með breytingum í möppum Comment[it]=Sorveglia i cambiamenti nelle cartelle Comment[ja]=ディレクトリの変更を監視します Comment[ko]=디렉터리 변경 사항을 감시합니다 -Comment[lt]=Stebi aplanko pakeitimus +Comment[lt]=Stebi pakeitimus kataloguose Comment[nl]=Mappen monitoren op wijzigingen Comment[nn]=Overvakar mapper for endringar Comment[pa]=ਬਦਲਣ ਲਈ ਮਾਨੀਟਰਾਂ ਦੀਆਂ ਦਿਸ਼ਾਵਾਂ diff --git a/kioslave/desktop/directory.trash b/kioslave/desktop/directory.trash --- a/kioslave/desktop/directory.trash +++ b/kioslave/desktop/directory.trash @@ -21,6 +21,7 @@ Name[ja]=ごみ箱 Name[ko]=휴지통 Name[lt]=Šiukšlinė +Name[lv]=Miskaste Name[nl]=Prullenbak Name[nn]=Papirkorg Name[pa]=ਰੱਦੀ @@ -36,12 +37,14 @@ Name[sr@ijekavianlatin]=Smeće Name[sr@latin]=Smeće Name[sv]=Papperskorg +Name[tg]=Сабад Name[tr]=Çöp Name[uk]=Смітник Name[x-test]=xxTrashxx Name[zh_CN]=回收站 Name[zh_TW]=垃圾桶 Comment=Contains removed files +Comment[ast]=Contién ficheros desaniciaos Comment[ca]=Conté els fitxers eliminats Comment[ca@valencia]=Conté els fitxers eliminats Comment[cs]=Obsahuje odstraněné soubory @@ -62,7 +65,8 @@ Comment[it]=Contiene i file rimossi Comment[ja]=削除されたファイルを保管します Comment[ko]=삭제된 파일 및 폴더가 있습니다 -Comment[lt]=Čia yra ištrinti failai +Comment[lt]=Čia yra pašalinti failai +Comment[lv]=Satur izmestās datnes Comment[nl]=Bevat de verwijderde bestanden Comment[nn]=Inneheld sletta filer Comment[pa]=ਹਟਾਈਆਂ ਫਾਇਲਾਂ ਰੱਖਦਾ ਹੈ @@ -78,6 +82,7 @@ Comment[sr@ijekavianlatin]=Sadrži uklonjene fajlove Comment[sr@latin]=Sadrži uklonjene fajlove Comment[sv]=Innehåller borttagna filer +Comment[tg]=Дорои файлҳои нестшуда мебошад Comment[tr]=Silinen dosyaları içerir Comment[uk]=Містить вилучені файли Comment[x-test]=xxContains removed filesxx diff --git a/kioslave/desktop/kio_desktop.cpp b/kioslave/desktop/kio_desktop.cpp --- a/kioslave/desktop/kio_desktop.cpp +++ b/kioslave/desktop/kio_desktop.cpp @@ -67,7 +67,7 @@ void DesktopProtocol::checkLocalInstall() { -#ifndef Q_WS_WIN +#ifndef Q_OS_WIN // QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) returns the home dir // if the desktop folder doesn't exist, so verify its result QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); @@ -136,7 +136,7 @@ QString DesktopProtocol::desktopFile(KIO::UDSEntry &entry) const { const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME); - if (name == QLatin1String(".") || name == QLatin1String("..")) + if (name == QLatin1Char('.') || name == QLatin1String("..")) return QString(); QUrl url = processedUrl(); @@ -174,7 +174,7 @@ // Set a descriptive display name for the root item if (requestedUrl().path() == QLatin1String("/") - && entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String(".")) { + && entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1Char('.')) { entry.replace(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Desktop Folder")); } diff --git a/kioslave/desktop/tests/kio_desktop_test.cpp b/kioslave/desktop/tests/kio_desktop_test.cpp --- a/kioslave/desktop/tests/kio_desktop_test.cpp +++ b/kioslave/desktop/tests/kio_desktop_test.cpp @@ -25,6 +25,16 @@ #include #include #include +#include + +static void doUndo() // see FileUndoManagerTest::doUndo() +{ + QEventLoop eventLoop; + QObject::connect(KIO::FileUndoManager::self(), &KIO::FileUndoManager::undoJobFinished, + &eventLoop, &QEventLoop::quit); + KIO::FileUndoManager::self()->undo(); + eventLoop.exec(QEventLoop::ExcludeUserInputEvents); // wait for undo job to finish +} class TestDesktop : public QObject { @@ -93,15 +103,15 @@ QCOMPARE(QFileInfo(localLink).symLinkTarget(), source); // Now try changing the link target, without Overwrite -> error - linkJob = KIO::symlink(m_testFileName + "2", desktopLink, KIO::HideProgressInfo); + linkJob = KIO::symlink(m_testFileName + QLatin1Char('2'), desktopLink, KIO::HideProgressInfo); QVERIFY(!linkJob->exec()); QCOMPARE(linkJob->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); // Now try changing the link target, with Overwrite (bug 360487) - linkJob = KIO::symlink(m_testFileName + "3", desktopLink, KIO::Overwrite | KIO::HideProgressInfo); + linkJob = KIO::symlink(m_testFileName + QLatin1Char('3'), desktopLink, KIO::Overwrite | KIO::HideProgressInfo); QVERIFY(linkJob->exec()); QVERIFY(QFileInfo(localLink).isSymLink()); - QCOMPARE(QFileInfo(localLink).symLinkTarget(), source + "3"); + QCOMPARE(QFileInfo(localLink).symLinkTarget(), source + QLatin1Char('3')); } void testRename_data() @@ -167,6 +177,27 @@ } } + void testTrashAndUndo() + { + // Given a file on the desktop... + const QString localPath = m_desktopPath + '/' + m_testFileName; + QVERIFY(QFile::exists(localPath)); + + // ...moved to the trash + const QUrl desktopUrl("desktop:/" + m_testFileName); + KIO::Job *job = KIO::trash({desktopUrl}, KIO::HideProgressInfo); + job->setUiDelegate(nullptr); + KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, {desktopUrl}, QUrl(QStringLiteral("trash:/")), job); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + QVERIFY(!QFile::exists(localPath)); + + // When the user calls undo + doUndo(); + + // Then the file should re-appear + QVERIFY(QFile::exists(localPath)); + } + private: QString m_desktopPath; QString m_testFileName; diff --git a/klipper/CMakeLists.txt b/klipper/CMakeLists.txt --- a/klipper/CMakeLists.txt +++ b/klipper/CMakeLists.txt @@ -24,7 +24,7 @@ find_package(KF5Prison ${KF5_MIN_VERSION}) set_package_properties(KF5Prison PROPERTIES DESCRIPTION "Prison library" - URL "http://projects.kde.org/prison" + URL "https://commits.kde.org/prison" TYPE OPTIONAL PURPOSE "Needed to create mobile barcodes from clipboard data" ) @@ -46,7 +46,6 @@ KF5::CoreAddons KF5::DBusAddons KF5::GlobalAccel - KF5::IconThemes KF5::KIOWidgets KF5::Notifications KF5::Service @@ -80,7 +79,6 @@ KF5::ConfigGui KF5::CoreAddons # KUrlMimeData KF5::GlobalAccel - KF5::IconThemes KF5::KIOWidgets # PreviewJob KF5::Plasma KF5::Notifications @@ -104,9 +102,5 @@ add_subdirectory(autotests) endif() -if (${ECM_VERSION} STRGREATER "5.58.0") - install( FILES klipper.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) -else() - install( FILES klipper.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) -endif() +install( FILES klipper.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) diff --git a/klipper/actionsconfig.ui b/klipper/actionsconfig.ui --- a/klipper/actionsconfig.ui +++ b/klipper/actionsconfig.ui @@ -91,7 +91,7 @@ - Click on a highlighted item's column to change it. "%s" in a command will be replaced with the clipboard contents.<br>For more information about regular expressions, you could have a look at the <a href="http://en.wikipedia.org/wiki/Regular_expression">Wikipedia entry about this topic</a>. + Click on a highlighted item's column to change it. "%s" in a command will be replaced with the clipboard contents.<br>For more information about regular expressions, you could have a look at the <a href="https://en.wikipedia.org/wiki/Regular_expression">Wikipedia entry about this topic</a>. Qt::RichText diff --git a/klipper/autotests/CMakeLists.txt b/klipper/autotests/CMakeLists.txt --- a/klipper/autotests/CMakeLists.txt +++ b/klipper/autotests/CMakeLists.txt @@ -21,6 +21,7 @@ Qt5::Test Qt5::Widgets # QAction KF5::CoreAddons # KUrlMimeData + KF5::I18n ) add_test(NAME klipper-testHistory COMMAND testHistory) ecm_mark_as_test(testHistory) @@ -43,6 +44,7 @@ Qt5::Test Qt5::Widgets # QAction KF5::CoreAddons # KUrlMimeData + KF5::I18n ) add_test(NAME klipper-testHistoryModel COMMAND testHistoryModel) ecm_mark_as_test(testHistoryModel) diff --git a/klipper/autotests/historytest.cpp b/klipper/autotests/historytest.cpp --- a/klipper/autotests/historytest.cpp +++ b/klipper/autotests/historytest.cpp @@ -37,8 +37,8 @@ void HistoryTest::testSetMaxSize() { QScopedPointer history(new History(nullptr)); - QSignalSpy changedSpy(history.data(), SIGNAL(changed())); - QSignalSpy topSpy(history.data(), SIGNAL(topChanged())); + QSignalSpy changedSpy(history.data(), &History::changed); + QSignalSpy topSpy(history.data(), &History::topChanged); QVERIFY(history->empty()); QCOMPARE(history->maxSize(), 0u); QVERIFY(changedSpy.isEmpty()); @@ -86,8 +86,8 @@ void HistoryTest::testInsertRemove() { QScopedPointer history(new History(nullptr)); - QSignalSpy topSpy(history.data(), SIGNAL(topChanged())); - QSignalSpy topUserSelectedSpy(history.data(), SIGNAL(topIsUserSelectedSet())); + QSignalSpy topSpy(history.data(), &History::topChanged); + QSignalSpy topUserSelectedSpy(history.data(), &History::topIsUserSelectedSet); history->setMaxSize(10); QVERIFY(history->empty()); @@ -228,7 +228,7 @@ QVERIFY(history->first()); // and clear - QSignalSpy topSpy(history.data(), SIGNAL(topChanged())); + QSignalSpy topSpy(history.data(), &History::topChanged); QVERIFY(topSpy.isEmpty()); history->slotClear(); QVERIFY(history->empty()); @@ -259,7 +259,7 @@ void HistoryTest::testCycle() { QScopedPointer history(new History(nullptr)); - QSignalSpy topSpy(history.data(), SIGNAL(topChanged())); + QSignalSpy topSpy(history.data(), &History::topChanged); history->setMaxSize(10); QVERIFY(!history->nextInCycle()); QVERIFY(!history->prevInCycle()); diff --git a/klipper/autotests/modeltest.h b/klipper/autotests/modeltest.h --- a/klipper/autotests/modeltest.h +++ b/klipper/autotests/modeltest.h @@ -11,8 +11,8 @@ ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. +** conditions see https://qt.io/licensing. For further information +** use the contact form at https://qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser @@ -32,7 +32,7 @@ ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. +** met: https://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ diff --git a/klipper/autotests/modeltest.cpp b/klipper/autotests/modeltest.cpp --- a/klipper/autotests/modeltest.cpp +++ b/klipper/autotests/modeltest.cpp @@ -11,8 +11,8 @@ ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. +** conditions see https://www.qt.io/licensing. For further information +** use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser @@ -32,7 +32,7 @@ ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. +** met: https://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ diff --git a/klipper/configdialog.cpp b/klipper/configdialog.cpp --- a/klipper/configdialog.cpp +++ b/klipper/configdialog.cpp @@ -343,37 +343,6 @@ m_shortcutsWidget->allDefault(); } -// it does not make sense to port / enable this since KRegExpEditor is in a very bad shape. just keep this -// code here because it will probably help at a later point to port it when KRegExpEditor is again usable. -// 2007-10-20, uwolfer -#if 0 -void ListView::rename( Q3ListViewItem* item, int c ) -{ - bool gui = false; - if ( item->childCount() != 0 && c == 0) { - // This is the regular expression - if ( _configWidget->useGUIRegExpEditor() ) { - gui = true; - } - } - - if ( gui ) { - if ( ! _regExpEditor ) - _regExpEditor = KServiceTypeTrader::createInstanceFromQuery( "KRegExpEditor/KRegExpEditor", QString(), this ); - KRegExpEditorInterface *iface = qobject_cast(_regExpEditor); - - Q_ASSERT( iface ); - iface->setRegExp( item->text( 0 ) ); - - bool ok = _regExpEditor->exec(); - if ( ok ) - item->setText( 0, iface->regExp() ); - } - else - K3ListView::rename( item ,c ); -} -#endif - AdvancedWidget::AdvancedWidget( QWidget *parent ) : QWidget(parent) { diff --git a/klipper/editactiondialog.cpp b/klipper/editactiondialog.cpp --- a/klipper/editactiondialog.cpp +++ b/klipper/editactiondialog.cpp @@ -20,12 +20,12 @@ #include "editactiondialog.h" +#include #include #include #include "klipper_debug.h" #include -#include #include #include @@ -101,7 +101,7 @@ DESCRIPTION_COL = 2 }; QList m_commands; - QVariant displayData(ClipCommand* command, column_t colunm) const; + QVariant displayData(ClipCommand* command, column_t column) const; QVariant editData(ClipCommand* command, column_t column) const; QVariant decorationData(ClipCommand* command, column_t column) const; void setIconForCommand(ClipCommand& cmd); @@ -129,12 +129,7 @@ command = command.section( QLatin1Char(' '), 0, 0 ); } - QPixmap iconPix = KIconLoader::global()->loadIcon( - command, KIconLoader::Small, 0, - KIconLoader::DefaultState, - QStringList(), nullptr, true /* canReturnNull */ ); - - if ( !iconPix.isNull() ) { + if (QIcon::hasThemeIcon(command)) { cmd.icon = command; } else { cmd.icon.clear(); diff --git a/klipper/historyimageitem.h b/klipper/historyimageitem.h --- a/klipper/historyimageitem.h +++ b/klipper/historyimageitem.h @@ -37,7 +37,7 @@ } return false; } - const QPixmap& image() const override { return m_data; } + const QPixmap& image() const override; QMimeData* mimeData() const override; void write( QDataStream& stream ) const override; diff --git a/klipper/historyimageitem.cpp b/klipper/historyimageitem.cpp --- a/klipper/historyimageitem.cpp +++ b/klipper/historyimageitem.cpp @@ -16,10 +16,16 @@ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + #include "historyimageitem.h" -#include +#include "historymodel.h" + #include +#include +#include + +#include namespace { QByteArray compute_uuid(const QPixmap& data) { @@ -38,14 +44,15 @@ } QString HistoryImageItem::text() const { - if ( m_text.isNull() ) { - m_text = QStringLiteral( "%1x%2x%3 %4" ) - .arg( m_data.width() ) - .arg( m_data.height() ) - .arg( m_data.depth() ); + if (m_text.isNull()) { + m_text = + QStringLiteral("▨ ") + + i18n("%1x%2 %3bpp") + .arg(m_data.width()) + .arg(m_data.height()) + .arg(m_data.depth()); } return m_text; - } /* virtual */ @@ -60,3 +67,12 @@ return data; } +const QPixmap& HistoryImageItem::image() const { + if (m_model->displayImages()) { + return m_data; + } + static QPixmap imageIcon( + QIcon::fromTheme(QStringLiteral("view-preview")).pixmap(QSize(48, 48)) + ); + return imageIcon; +} diff --git a/klipper/historyitem.h b/klipper/historyitem.h --- a/klipper/historyitem.h +++ b/klipper/historyitem.h @@ -100,9 +100,12 @@ QByteArray next_uuid() const; void setModel(HistoryModel *model); + +protected: + HistoryModel *m_model; + private: QByteArray m_uuid; - HistoryModel *m_model; }; inline diff --git a/klipper/historyitem.cpp b/klipper/historyitem.cpp --- a/klipper/historyitem.cpp +++ b/klipper/historyitem.cpp @@ -30,8 +30,8 @@ #include "historymodel.h" HistoryItem::HistoryItem(const QByteArray& uuid) - : m_uuid(uuid) - , m_model(nullptr) + : m_model(nullptr) + , m_uuid(uuid) { } diff --git a/klipper/historymodel.h b/klipper/historymodel.h --- a/klipper/historymodel.h +++ b/klipper/historymodel.h @@ -46,6 +46,9 @@ int maxSize() const; void setMaxSize(int size); + bool displayImages() const; + void setDisplayImages(bool show); + void clear(); void moveToTop(const QByteArray &uuid); void moveTopToBack(); @@ -64,14 +67,23 @@ void moveToTop(int row); QList> m_items; int m_maxSize; + bool m_displayImages; QMutex m_mutex; }; inline int HistoryModel::maxSize() const { return m_maxSize; } +inline bool HistoryModel::displayImages() const { + return m_displayImages; +} + +inline void HistoryModel::setDisplayImages(bool show) { + m_displayImages = show; +} + Q_DECLARE_METATYPE(HistoryItemType) #endif diff --git a/klipper/historymodel.cpp b/klipper/historymodel.cpp --- a/klipper/historymodel.cpp +++ b/klipper/historymodel.cpp @@ -25,6 +25,7 @@ HistoryModel::HistoryModel(QObject *parent) : QAbstractListModel(parent) , m_maxSize(0) + , m_displayImages(true) , m_mutex(QMutex::Recursive) { } @@ -84,11 +85,11 @@ case Qt::DecorationRole: return item->image(); case Qt::UserRole: - return qVariantFromValue(qSharedPointerConstCast(item)); + return QVariant::fromValue(qSharedPointerConstCast(item)); case Qt::UserRole+1: return item->uuid(); case Qt::UserRole+2: - return qVariantFromValue(type); + return QVariant::fromValue(type); case Qt::UserRole+3: return item->uuid().toBase64(); case Qt::UserRole+4: diff --git a/klipper/klipper.h b/klipper/klipper.h --- a/klipper/klipper.h +++ b/klipper/klipper.h @@ -23,7 +23,7 @@ #include "config-klipper.h" -#include +#include #include #include #include @@ -145,6 +145,8 @@ void newClipData( QClipboard::Mode ); void slotClearClipboard(); + void slotHistoryChanged(); + void slotQuit(); void slotStartShowTimer(); @@ -159,9 +161,7 @@ QClipboard* m_clip; - QSharedPointer m_last; - - QTime m_showTimer; + QElapsedTimer m_showTimer; History* m_history; KlipperPopup *m_popup; diff --git a/klipper/klipper.cpp b/klipper/klipper.cpp --- a/klipper/klipper.cpp +++ b/klipper/klipper.cpp @@ -69,7 +69,7 @@ * * This avoids issues such as mouse-selections that immediately * disappear. - * pattern: Resource Acqusition is Initialisation (RAII) + * pattern: Resource Acquisition is Initialisation (RAII) * * (This is not threadsafe, so don't try to use such in threaded * applications). @@ -116,6 +116,7 @@ m_history = new History( this ); m_popup = new KlipperPopup(m_history); m_popup->setShowHelp(m_mode == KlipperMode::Standalone); + connect(m_history, &History::changed, this, &Klipper::slotHistoryChanged); connect(m_history, &History::changed, m_popup, &KlipperPopup::slotHistoryChanged); connect(m_history, &History::topIsUserSelectedSet, m_popup, &KlipperPopup::slotTopIsUserSelectedSet); @@ -288,7 +289,6 @@ void Klipper::clearClipboardHistory() { updateTimestamp(); - slotClearClipboard(); history()->slotClear(); saveSession(); } @@ -330,6 +330,8 @@ // this will cause it to loadSettings too setURLGrabberEnabled(m_bURLGrabber); history()->setMaxSize( KlipperSettings::maxClipItems() ); + history()->model()->setDisplayImages(!m_bIgnoreImages); + // Convert 4.3 settings if (KlipperSettings::synchronize() != 3) { // 2 was the id of "Ignore selection" radiobutton @@ -604,23 +606,24 @@ return HistoryItemPtr(); } Ignore lock( m_locklevel ); - HistoryItemPtr item = HistoryItem::create( clipData ); - bool saveHistory = true; - if (clipData->data(QStringLiteral("x-kde-passwordManagerHint")) == QByteArrayLiteral("secret")) { - saveHistory = false; - } - if (clipData->hasImage() && m_bIgnoreImages) { - saveHistory = false; + if (!(history()->empty())) { + if (m_bIgnoreImages && history()->first()->mimeData()->hasImage()) { + history()->remove(history()->first()); + } } - m_last = item; + HistoryItemPtr item = HistoryItem::create( clipData ); - if (saveHistory) { + bool saveToHistory = true; + if (clipData->data(QStringLiteral("x-kde-passwordManagerHint")) == QByteArrayLiteral("secret")) { + saveToHistory = false; + } + if (saveToHistory) { history()->insert( item ); } - return item; + return item; } void Klipper::newClipData( QClipboard::Mode mode ) @@ -636,6 +639,13 @@ } +void Klipper::slotHistoryChanged() +{ + if (history()->empty()) { + slotClearClipboard(); + } +} + // Protection against too many clipboard data changes. Lyx responds to clipboard data // requests with setting new clipboard data, so if Lyx takes over clipboard, // Klipper notices, requests this data, this triggers "new" clipboard contents @@ -697,7 +707,7 @@ // This won't quite work, but it's close enough for now. // The trouble is that the top selection =! top clipboard // but we don't track that yet. We will.... - auto top = m_last; + auto top = history()->first(); if ( top ) { setClipboard( *top, selectionMode ? Selection : Clipboard); } @@ -721,7 +731,7 @@ } if ( changed && clipEmpty && m_bNoNullClipboard ) { - auto top = m_last; + auto top = history()->first(); if ( top ) { // keep old clipboard after someone set it to null qCDebug(KLIPPER_LOG) << "Resetting clipboard (Prevent empty clipboard)"; @@ -992,7 +1002,6 @@ KMessageBox::Dangerous); if (clearHist == KMessageBox::Yes) { history()->slotClear(); - slotClearClipboard(); saveHistory(); } diff --git a/klipper/klipper.desktop b/klipper/klipper.desktop --- a/klipper/klipper.desktop +++ b/klipper/klipper.desktop @@ -2,6 +2,7 @@ Name=Klipper Name[af]=Klipper Name[ar]=مقصّ.ك +Name[ast]=Klipper Name[be]=Klipper Name[be@latin]=Klipper Name[bg]=Klipper @@ -75,7 +76,6 @@ Name[sv]=Klipper Name[ta]=க்ளிப்பர் Name[te]=క్లిప్పర్ -Name[tg]=Клиппер Name[th]=คลิปเปอร์ Name[tr]=Klipper Name[ug]=Klipper @@ -98,7 +98,7 @@ GenericName[br]=Ostilh ar golver GenericName[bs]=Alatka za klipbord GenericName[ca]=Eina de porta-retalls -GenericName[ca@valencia]=Eina de porta-retalls +GenericName[ca@valencia]=Eina del porta-retalls GenericName[cs]=Program pro práci se schránkou GenericName[csb]=Nôrzãdze tacnika GenericName[cy]=Offeryn Gludfwrdd @@ -133,7 +133,7 @@ GenericName[km]=ឧបករណ៍​ក្ដារ​តម្បៀត​ខ្ទាស់ GenericName[kn]=ಹಿಡಿಕೆ ಕಟ್ಟು (ಕ್ಲಿಪ್ ಬೋರ್ಡ್) ಸಲಕರಣೆ GenericName[ko]=클립보드 도구 -GenericName[lt]=Iškarpinės tvarkytuvė +GenericName[lt]=Iškarpinės įrankis GenericName[lv]=Starpliktuves rīks GenericName[mai]=क्लिपबोर्ड अओजार GenericName[mk]=Алатка за табла со исечоци @@ -162,7 +162,7 @@ GenericName[sv]=Klippbordsverktyg GenericName[ta]=தற்காலிக கருவி GenericName[te]=క్లిప్ బోర్డ్ పనిముట్టు -GenericName[tg]=Утилита для буфера обмена +GenericName[tg]=Абзори ҳофизаи муваққатӣ GenericName[th]=เครื่องมือคลิปบอร์ด GenericName[tr]=Pano Aracı GenericName[ug]=چاپلاش تاختىسى قورالى @@ -186,6 +186,9 @@ X-DBUS-ServiceName=org.kde.klipper X-KDE-UniqueApplet=true X-KDE-autostart-condition=klipperrc:General:AutoStart:false +# Tell AppStream generators not to merge info in this file so Discover and other +# software centers don't show two Klipper entries +X-AppStream-Ignore=true OnlyShowIn=KDE; Categories=Qt;KDE;Utility;X-KDE-Utilities-Desktop; Comment=A cut & paste history utility @@ -196,7 +199,7 @@ Comment[bg]=Инструмент за управление на операциите по копиране и поставяне Comment[bs]=Alatka za istorijat isecanja i naljepljivanja Comment[ca]=Una utilitat de l'historial per retallar i enganxar -Comment[ca@valencia]=Una utilitat de l'historial per retallar i enganxar +Comment[ca@valencia]=Una utilitat de l'historial per a retallar i apegar Comment[cs]=Nástroj pro historii práce se schránkou Comment[csb]=Nôrzãdze trzëmôjące historëjã tacnika Comment[da]=Et værktøj med historik til at klippe ud og indsætte @@ -229,7 +232,7 @@ Comment[km]=កាត់ និង​បិទភ្ជាប់​ឧបករណ៍​ប្រើប្រាស់​ប្រវត្តិ Comment[kn]=ಕತ್ತರಿಸು ಮತ್ತು ಅಂಟಿಸು ಚರಿತ್ರೆ ಸೌಲಭ್ಯ Comment[ko]=자르고 붙인 기록 도구 -Comment[lt]=„Iškirpti ir padėti“ istorijos pagalbinė programa +Comment[lt]=Iškirpimo ir įdėjimo istorijos paslaugų programa Comment[lv]=Izgriešanas un ielīmēšanas vēstures rīks Comment[mai]=काटू आओर साटू इतिहास यूटिलिटी Comment[mk]=Алатка за историјат на сечење и вметнување @@ -257,7 +260,6 @@ Comment[sv]=Ett verktyg med historik för klipp ut och klistra in Comment[ta]=A cut & paste history utility Comment[te]=కత్తిరించు & అతికించు చరిత్ర సౌలభ్యం -Comment[tg]=История буфера обмена Comment[th]=เครื่องมือดูประวัติการตัดและวาง Comment[tr]=Bir kes & yapıştır geçmişi aracı Comment[ug]=كەس ۋە چاپلا تارىخىنى باشقۇرۇش قورالى @@ -267,4 +269,3 @@ Comment[x-test]=xxA cut & paste history utilityxx Comment[zh_CN]=管理剪切和粘贴历史的工具 Comment[zh_TW]=剪貼紀錄公用程式 - diff --git a/klipper/klipperrc.desktop b/klipper/klipperrc.desktop --- a/klipper/klipperrc.desktop +++ b/klipper/klipperrc.desktop @@ -47,7 +47,7 @@ Description[hsb]=jpeg-wobraz Description[hu]=JPEG kép Description[ia]=Jpeg-Image -Description[id]=Image-Jpeg +Description[id]=Citra-Jpeg Description[is]=Jpeg mynd Description[it]=Immagine JPEG Description[ja]=JPEG 画像 @@ -57,7 +57,7 @@ Description[kn]=ಜೆಪೆಗ್-ಚಿತ್ರ Description[ko]=Jpeg-그림 Description[ku]=Wêneyê Jpeg -Description[lt]=Jpeg paveikslėlis +Description[lt]=Jpeg paveikslas Description[lv]=Jpeg attēls Description[mai]=जेपीईजी-छवि Description[mk]=Jpeg-слика @@ -114,7 +114,7 @@ Description[bn_IN]=Gwenview আরম্ভ করুন (&G) Description[bs]=Pokreni &Gvenvju Description[ca]=Llança el &Gwenview -Description[ca@valencia]=Llança el &Gwenview +Description[ca@valencia]=Llança &Gwenview Description[cs]=Spustit &Gwenview Description[csb]=Zrëszë &Przezérnik òbrôzów Description[da]=Start &Gwenview @@ -172,7 +172,6 @@ Description[sv]=Starta &Gwenview Description[ta]=&ஜிவென்யூ ஏவுக Description[te]=&Gwenview ను దించుము -Description[tg]=Кушодани &Gwenview Description[th]=เรียกใช้งาน '&Gwenview' Description[tr]=&Gwenview Uygulamasını Çalıştır Description[ug]=&Gwenview نى قوزغات @@ -233,7 +232,7 @@ Description[kn]=ಜಾಲ-URL Description[ko]=웹-URL Description[ku]=URL ya torê -Description[lt]=Žiniatinklio URL +Description[lt]=Saityno URL Description[lv]=Tīmekļa-URL Description[mai]=वेब-यूआरएल Description[mk]=Веб-URL @@ -262,7 +261,6 @@ Description[sv]=Webbadress Description[ta]=வலை-வலைப்பின்னல் Description[te]=వెబ్ యూ ఆర్ ఎల్ -Description[tg]=Суроғаи Интернет Description[th]=ที่อยู่ URL ของเว็บ Description[tr]=Web-URL Description[ug]=Web-URL @@ -289,7 +287,7 @@ Description[bn_IN]=ডিফল্ট ব্রাউজার সহযোগে প্রদর্শিত হবে (&d) Description[bs]=Otvori &podrazumijevanim pregledačem Description[ca]=Obre amb el navega&dor per omissió -Description[ca@valencia]=Obri amb el navega&dor per omissió +Description[ca@valencia]=Obri amb el navega&dor per defecte Description[cs]=Otevřít pomocí vý&chozího prohlížeče Description[csb]=Òtemkni w &domëslnym przezérnikù Description[da]=Åbn med stan&dard browser @@ -322,7 +320,7 @@ Description[kn]=ಪೂರ್ವನಿಯೋಜಿ&ತ ವೀಕ್ಷಕದೊಂದಿಗೆ ತೆರೆ Description[ko]=기본 브라우저로 열기(&D) Description[ku]=Bi geroka &standard veke -Description[lt]=Atverti su &numatyta naršykle +Description[lt]=Atverti naudojant &numatytąją naršyklę Description[lv]=Atvērt ar &noklusēto pārlūku Description[mk]=Отвори со стан&дарден прелистувач Description[ml]=&സഹജമായ ബ്രൌസറില്‍ തുറക്കുക @@ -347,7 +345,6 @@ Description[sv]=Öppna med &förvald webbläsare Description[ta]=&இயல்பிருப்பு உலாவியுடன் திறக்க Description[te]=అప్రమేయ అన్వేషణితో తెరువుము (&d) -Description[tg]=Кушодан бо намоишгари Интернети &стандартӣ Description[th]=เปิดด้วยเบราเซอร์&ปริยาย Description[tr]=&Öntanımlı tarayıcı ile aç Description[ug]=كۆڭۈلدىكى تور كۆرگۈدە ئاچ(&D) @@ -373,7 +370,7 @@ Description[br]=Digeriñ gant &Konqueror Description[bs]=Otvori &Konquerorem Description[ca]=Obre amb el &Konqueror -Description[ca@valencia]=Obri amb el &Konqueror +Description[ca@valencia]=Obri amb &Konqueror Description[cs]=Otevřít pomocí &Konqueroru Description[csb]=Òtemkni w &Konquerorze Description[cy]=Agor gyda &Konqueror @@ -409,7 +406,7 @@ Description[kn]=ಕಾಂ&ಕರರ್ ನೊಂದಿಗೆ ತೆರೆ Description[ko]=Konqueror로 열기(&K) Description[ku]=Bi &Konqueror veke -Description[lt]=Atverti su &Konqueror +Description[lt]=Atverti naudojant &Konqueror Description[lv]=Atvērt ar &Konqueror Description[mai]=कान्करर केर सँग खोलू (&K) Description[mk]=Отвори со &Konqueror @@ -438,7 +435,6 @@ Description[sv]=Öppna med &Konqueror Description[ta]=கான்கொரர் உடன் திற Description[te]=(&K) కాంకెరర్ తొ తెరువు -Description[tg]=Кушодан бо &Konqueror Description[th]=เปิดด้วย '&คอนเควอร์เรอร์' Description[tr]=&Konqueror ile Aç Description[ug]=&Konqueror دا ئاچ @@ -467,7 +463,7 @@ Description[br]=Digeriñ gant &Mozilla Description[bs]=Otvori &Mozilom Description[ca]=Obre amb el &Mozilla -Description[ca@valencia]=Obri amb el &Mozilla +Description[ca@valencia]=Obri amb &Mozilla Description[cs]=Otevřít pomocí &Mozilla Description[csb]=Òtemkni w &Mozillë Description[cy]=Agor gyda &Mozilla @@ -503,7 +499,7 @@ Description[kn]=(&M)ಮೋಜಿಲ್ಲಾದೊಡನೆ ತೆರೆ Description[ko]=Mozilla로 열기(&M) Description[ku]=Bi &Mozilla veke -Description[lt]=Atverti su &Mozilla +Description[lt]=Atverti naudojant &Mozilla Description[lv]=Atvērt ar &Mozilla Description[mai]=मोजिला केर सँग खोलू (&M) Description[mk]=Отвори со &Mozilla @@ -532,7 +528,6 @@ Description[sv]=Öppna med &Mozilla Description[ta]=மொசில்லாவுடன் திற Description[te]=(&M) మొజిల్లా తొ తెరువు -Description[tg]=Кушодан бо &Mozilla Description[th]=เปิดด้วย '&มอซซิลลา' Description[tr]=&Mozilla ile Aç Description[ug]=&Mozilla دا ئاچ @@ -626,7 +621,6 @@ Description[sv]=Skicka &webbadress Description[ta]=வலைப்பின்னலை அனுப்பு Description[te]=(&U) యూ ఆర్ ఎల్ పంపించు -Description[tg]=Ирсоли &URL Description[th]=ส่งที่อยู่ &URL Description[tr]=&URL Gönder Description[ug]=URL ئەۋەت(&U) @@ -655,7 +649,7 @@ Description[br]=Digeriñ gant &Firefox Description[bs]=Otvori &Fajerfoksom Description[ca]=Obre amb el &Firefox -Description[ca@valencia]=Obri amb el &Firefox +Description[ca@valencia]=Obri amb &Firefox Description[cs]=Otevřít pomocí &Firefox Description[csb]=Òtemkni w &Firefokse Description[da]=Åbn med &Firefox @@ -690,7 +684,7 @@ Description[kn]=(&F)ಫೈರ್ ಫಾಕ್ಸ್ ನೊಂದಿಗೆ ತೆರೆ Description[ko]=Firefox로 열기(&F) Description[ku]=Bi &Firefoxê Veke -Description[lt]=Atverti su &Firefox +Description[lt]=Atverti naudojant &Firefox Description[lv]=Atvērt ar &Firefox Description[mai]=फायरफाक्स केर सँग खोलू (&F) Description[mk]=Отвори со &Firefox @@ -718,7 +712,6 @@ Description[sv]=Öppna med &Firefox Description[ta]=&பயர்பாசுடன் திற Description[te]=(&F) మొజిల్లా తొ తెరువు -Description[tg]=Кушодан бо &Firefox Description[th]=เปิดด้วย 'ไ&ฟร์ฟอกซ์' Description[tr]=&Firefox ile Aç Description[ug]=ئوتۈلكىدە ئاچ(&F) @@ -811,7 +804,6 @@ Description[sv]=Ski&cka sida Description[ta]=பக்கத்தை அனுப்பு Description[te]=(&P) పుట ను పంపించు -Description[tg]=Ирсоли &саҳифа Description[th]=ส่งหน้าเ&ว็บ Description[tr]=&Sayfayı Gönder Description[ug]=بەت ئەۋەت(&P) @@ -902,7 +894,6 @@ Description[sv]=Webbadress för e-post Description[ta]=அஞ்சல்-வலைப்பின்னல் Description[te]=యూ ఆర్ ఎల్ ను మెయిల్ చెయ్యి -Description[tg]=Суроғаи почта Description[th]=ที่อยู่ URL ของจดหมาย Description[tr]=Adres-Postala Description[ug]=خەت URL @@ -931,7 +922,7 @@ Description[br]=Lañsañ &Kmail Description[bs]=Pokreni &K‑poštu Description[ca]=Llança el &Kmail -Description[ca@valencia]=Llança el &Kmail +Description[ca@valencia]=Llança &Kmail Description[cs]=Spustit &Kmail Description[csb]=Zrëszë &KMail Description[cy]=Cychwyn &KMail @@ -996,7 +987,6 @@ Description[sv]=Starta &Kmail Description[ta]=Kஅஞ்சலை இறக்கு Description[te]=&Kmail దించుము -Description[tg]=Кушодани &Kmail Description[th]=เรียกใช้งาน 'รับ/ส่งจดหมาย-&K' Description[tr]=KMail Uygulamasını Ç&alıştır Description[ug]=&Kmail قوزغات @@ -1023,7 +1013,7 @@ Description[br]=Lañsañ &mutt Description[bs]=Pokreni &Mat Description[ca]=Llança el &mutt -Description[ca@valencia]=Llança el &mutt +Description[ca@valencia]=Llança &mutt Description[cs]=Spustit &mutt Description[csb]=Zrëszë &mutt Description[cy]=Cychwyn &mutt @@ -1088,7 +1078,6 @@ Description[sv]=Starta &Mutt Description[ta]=muttஐ இறக்கு Description[te]=&mutt దించుము -Description[tg]=Кушодани &mutt Description[th]=เรียกใช้งาน '&mutt' Description[tr]=&Mutt Uygulamasını Çalıştır Description[ug]=&mutt قوزغات @@ -1107,6 +1096,7 @@ Description=Text File Description[af]=Teks Lêer Description[ar]=ملف نصّي +Description[ast]=Ficheru de testu Description[be]=Тэкставыя файлы Description[be@latin]=Tekstavy fajł Description[bg]=Текстов файл @@ -1151,8 +1141,8 @@ Description[kn]=ಪಠ್ಯ ಕಡತ Description[ko]=텍스트 파일 Description[ku]=Pelê Nivîsê -Description[lt]=Teksto failas -Description[lv]=Teksta fails +Description[lt]=Tekstinis failas +Description[lv]=Teksta datne Description[mai]=पाठ फाइल: Description[mk]=Текстуална датотека Description[ml]=പദാവലി ഫയല്‍ @@ -1210,7 +1200,7 @@ Description[br]=Lañsañ K&Write Description[bs]=Pokreni &K‑pisanje Description[ca]=Llança el K&Write -Description[ca@valencia]=Llança el K&Write +Description[ca@valencia]=Llança K&Write Description[cs]=Spustit K&Write Description[csb]=Zrëszë editorã K&Write Description[cy]=Cychwyn K&Write @@ -1275,7 +1265,6 @@ Description[sv]=Starta K&write Description[ta]= K&Writeஐ இறக்கு Description[te]=K&Write దించుము -Description[tg]=Кушодани K&Write Description[th]=เรียกใช้งาน 'K&Write' Description[tr]=K&Write Uygulamasını Çalıştır Description[ug]=K&Write قوزغات @@ -1339,7 +1328,7 @@ Description[ko]=로컬 파일 URL Description[ku]=URL'ya pelê herêmî Description[lt]=Vietinio failo URL -Description[lv]=Lokāla faila URL +Description[lv]=Lokālas datnes URL Description[mai]=फाइल यूआरएल निर्धारित करू Description[mk]=URL на локалната датотека Description[ml]=ലോക്കല്‍ ഫയലിനുള്ള യുആര്‍എല്‍ @@ -1367,7 +1356,6 @@ Description[sv]=Webbadress för lokal fil Description[ta]=உள் கோப்பு வலைப்பின்னல் Description[te]=స్థానిక దస్త్ర యూ ఆర్ ఎల్ -Description[tg]=Ҷойгиршавии файли дохилӣ Description[th]=ที่อยู่ของแฟ้มในระบบ Description[tr]=Yerel dosya adresi Description[ug]=يەرلىك ھۆججەت URL @@ -1462,7 +1450,6 @@ Description[sv]=Skicka &webbadress Description[ta]=வலைப்பின்னலை அனுப்பு Description[te]=(&U) యూ ఆర్ ఎల్ పంపించు -Description[tg]=Ирсоли &URL Description[th]=ส่งที่อยู่ &URL Description[tr]=&URL Gönder Description[ug]=URL ئەۋەت(&U) @@ -1527,7 +1514,7 @@ Description[ko]=파일 보내기(&F) Description[ku]=Pelî &Bişeyîne Description[lt]=Siųsti &failą -Description[lv]=Sūtīt &failu +Description[lv]=Sūtīt &datni Description[mai]=फाइल भेजू (&F) Description[mk]=Испрати &датотека Description[ml]=&ഫയല്‍ അയയ്ക്കുക @@ -1556,7 +1543,7 @@ Description[sv]=Skicka &fil Description[ta]=கோப்பை அனுப்பு Description[te]=(&F) దస్త్రాన్ని పంపించు -Description[tg]=Ирсоли &файл +Description[tg]=&Фиристодани файл Description[th]=ส่งแ&ฟ้ม Description[tr]=Dosya &Gönder Description[ug]=ھۆججەت ئەۋەت(&F) @@ -1647,7 +1634,6 @@ Description[sv]=Gopher-webbadress Description[ta]=கோப்பர் வலைப்பின்னல் Description[te]=గొఫర్ యూ ఆర్ ఎల్ -Description[tg]=Суроғаи Gopher Description[th]=ที่อยู่ URL ของโกเฟอร์ Description[tr]=Gopher adresi Description[ug]=Gopher URL @@ -1742,7 +1728,6 @@ Description[sv]=Skicka &webbadress Description[ta]=வலைப்பின்னலை அனுப்பு Description[te]=(&U) యూ ఆర్ ఎల్ పంపించు -Description[tg]=Ирсоли &URL Description[th]=ส่งที่อยู่ &URL Description[tr]=&URL Gönder Description[ug]=URL ئەۋەت(&U) @@ -1807,7 +1792,7 @@ Description[ko]=파일 보내기(&F) Description[ku]=Pelî &Bişeyîne Description[lt]=Siųsti &failą -Description[lv]=Sūtīt &failu +Description[lv]=Sūtīt &datni Description[mai]=फाइल भेजू (&F) Description[mk]=Испрати &датотека Description[ml]=&ഫയല്‍ അയയ്ക്കുക @@ -1836,7 +1821,7 @@ Description[sv]=Skicka &fil Description[ta]=கோப்பை அனுப்பு Description[te]=(&F) దస్త్రాన్ని పంపించు -Description[tg]=Ирсоли &файл +Description[tg]=&Фиристодани файл Description[th]=ส่งแ&ฟ้ม Description[tr]=Dosya &Gönder Description[ug]=ھۆججەت ئەۋەت(&F) @@ -1928,7 +1913,6 @@ Description[sv]=FTP-webbadress Description[ta]=ftpவலைப்பின்னல் Description[te]=ఎఫ్ టి పి యూ ఆర్ ఎల్ -Description[tg]=Суроғаи FTP Description[th]=ที่อยู่ URL ของ ftp Description[tr]=ftp adresi Description[ug]=ftp تور ئادرېسى(URL) @@ -1955,7 +1939,7 @@ Description[bn_IN]=ডিফল্ট ব্রাউজার সহযোগে প্রদর্শিত হবে (&d) Description[bs]=Otvori &podrazumijevanim pregledačem Description[ca]=Obre amb el navega&dor per omissió -Description[ca@valencia]=Obri amb el navega&dor per omissió +Description[ca@valencia]=Obri amb el navega&dor per defecte Description[cs]=Otevřít pomocí vý&chozího prohlížeče Description[csb]=Òtemkni w &domëslnym przezérnikù Description[da]=Åbn med stan&dard browser @@ -1988,7 +1972,7 @@ Description[kn]=ಪೂರ್ವನಿಯೋಜಿ&ತ ವೀಕ್ಷಕದೊಂದಿಗೆ ತೆರೆ Description[ko]=기본 브라우저로 열기(&D) Description[ku]=Bi geroka &standard veke -Description[lt]=Atverti su &numatyta naršykle +Description[lt]=Atverti naudojant &numatytąją naršyklę Description[lv]=Atvērt ar &noklusēto pārlūku Description[mk]=Отвори со стан&дарден прелистувач Description[ml]=&സഹജമായ ബ്രൌസറില്‍ തുറക്കുക @@ -2013,7 +1997,6 @@ Description[sv]=Öppna med &förvald webbläsare Description[ta]=&இயல்பிருப்பு உலாவியுடன் திறக்க Description[te]=అప్రమేయ అన్వేషణితో తెరువుము (&d) -Description[tg]=Кушодан бо намоишгари Интернети &стандартӣ Description[th]=เปิดด้วยเบราเซอร์&ปริยาย Description[tr]=&Öntanımlı tarayıcı ile aç Description[ug]=كۆڭۈلدىكى تور كۆرگۈدە ئاچ(&D) @@ -2039,7 +2022,7 @@ Description[br]=Digeriñ gant &Konqueror Description[bs]=Otvori &Konquerorem Description[ca]=Obre amb el &Konqueror -Description[ca@valencia]=Obri amb el &Konqueror +Description[ca@valencia]=Obri amb &Konqueror Description[cs]=Otevřít pomocí &Konqueroru Description[csb]=Òtemkni w &Konquerorze Description[cy]=Agor gyda &Konqueror @@ -2075,7 +2058,7 @@ Description[kn]=ಕಾಂ&ಕರರ್ ನೊಂದಿಗೆ ತೆರೆ Description[ko]=Konqueror로 열기(&K) Description[ku]=Bi &Konqueror veke -Description[lt]=Atverti su &Konqueror +Description[lt]=Atverti naudojant &Konqueror Description[lv]=Atvērt ar &Konqueror Description[mai]=कान्करर केर सँग खोलू (&K) Description[mk]=Отвори со &Konqueror @@ -2104,7 +2087,6 @@ Description[sv]=Öppna med &Konqueror Description[ta]=கான்கொரர் உடன் திற Description[te]=(&K) కాంకెరర్ తొ తెరువు -Description[tg]=Кушодан бо &Konqueror Description[th]=เปิดด้วย '&คอนเควอร์เรอร์' Description[tr]=&Konqueror ile Aç Description[ug]=&Konqueror دا ئاچ @@ -2132,7 +2114,7 @@ Description[br]=Digeriñ gant &Mozilla Description[bs]=Otvori &Mozilom Description[ca]=Obre amb el &Mozilla -Description[ca@valencia]=Obri amb el &Mozilla +Description[ca@valencia]=Obri amb &Mozilla Description[cs]=Otevřít pomocí &Mozilla Description[csb]=Òtemkni w &Mozillë Description[cy]=Agor gyda &Mozilla @@ -2168,7 +2150,7 @@ Description[kn]=(&M)ಮೋಜಿಲ್ಲಾದೊಡನೆ ತೆರೆ Description[ko]=Mozilla로 열기(&M) Description[ku]=Bi &Mozilla veke -Description[lt]=Atverti su &Mozilla +Description[lt]=Atverti naudojant &Mozilla Description[lv]=Atvērt ar &Mozilla Description[mai]=मोजिला केर सँग खोलू (&M) Description[mk]=Отвори со &Mozilla @@ -2197,7 +2179,6 @@ Description[sv]=Öppna med &Mozilla Description[ta]=மொசில்லாவுடன் திற Description[te]=(&M) మొజిల్లా తొ తెరువు -Description[tg]=Кушодан бо &Mozilla Description[th]=เปิดด้วย '&มอซซิลลา' Description[tr]=&Mozilla ile Aç Description[ug]=&Mozilla دا ئاچ @@ -2291,7 +2272,6 @@ Description[sv]=Skicka &webbadress Description[ta]=வலைப்பின்னலை அனுப்பு Description[te]=(&U) యూ ఆర్ ఎల్ పంపించు -Description[tg]=Ирсоли &URL Description[th]=ส่งที่อยู่ &URL Description[tr]=&URL Gönder Description[ug]=URL ئەۋەت(&U) @@ -2356,7 +2336,7 @@ Description[ko]=파일 보내기(&F) Description[ku]=Pelî &Bişeyîne Description[lt]=Siųsti &failą -Description[lv]=Sūtīt &failu +Description[lv]=Sūtīt &datni Description[mai]=फाइल भेजू (&F) Description[mk]=Испрати &датотека Description[ml]=&ഫയല്‍ അയയ്ക്കുക @@ -2385,7 +2365,7 @@ Description[sv]=Skicka &fil Description[ta]=கோப்பை அனுப்பு Description[te]=(&F) దస్త్రాన్ని పంపించు -Description[tg]=Ирсоли &файл +Description[tg]=&Фиристодани файл Description[th]=ส่งแ&ฟ้ม Description[tr]=Dosya &Gönder Description[ug]=ھۆججەت ئەۋەت(&F) diff --git a/klipper/org.kde.klipper.desktop b/klipper/org.kde.klipper.desktop --- a/klipper/org.kde.klipper.desktop +++ b/klipper/org.kde.klipper.desktop @@ -2,6 +2,7 @@ Name=Klipper Name[af]=Klipper Name[ar]=مقصّ.ك +Name[ast]=Klipper Name[be]=Klipper Name[be@latin]=Klipper Name[bg]=Klipper @@ -75,7 +76,6 @@ Name[sv]=Klipper Name[ta]=க்ளிப்பர் Name[te]=క్లిప్పర్ -Name[tg]=Клиппер Name[th]=คลิปเปอร์ Name[tr]=Klipper Name[ug]=Klipper @@ -98,7 +98,7 @@ GenericName[br]=Ostilh ar golver GenericName[bs]=Alatka za klipbord GenericName[ca]=Eina de porta-retalls -GenericName[ca@valencia]=Eina de porta-retalls +GenericName[ca@valencia]=Eina del porta-retalls GenericName[cs]=Program pro práci se schránkou GenericName[csb]=Nôrzãdze tacnika GenericName[cy]=Offeryn Gludfwrdd @@ -133,7 +133,7 @@ GenericName[km]=ឧបករណ៍​ក្ដារ​តម្បៀត​ខ្ទាស់ GenericName[kn]=ಹಿಡಿಕೆ ಕಟ್ಟು (ಕ್ಲಿಪ್ ಬೋರ್ಡ್) ಸಲಕರಣೆ GenericName[ko]=클립보드 도구 -GenericName[lt]=Iškarpinės tvarkytuvė +GenericName[lt]=Iškarpinės įrankis GenericName[lv]=Starpliktuves rīks GenericName[mai]=क्लिपबोर्ड अओजार GenericName[mk]=Алатка за табла со исечоци @@ -162,7 +162,7 @@ GenericName[sv]=Klippbordsverktyg GenericName[ta]=தற்காலிக கருவி GenericName[te]=క్లిప్ బోర్డ్ పనిముట్టు -GenericName[tg]=Утилита для буфера обмена +GenericName[tg]=Абзори ҳофизаи муваққатӣ GenericName[th]=เครื่องมือคลิปบอร์ด GenericName[tr]=Pano Aracı GenericName[ug]=چاپلاش تاختىسى قورالى @@ -186,6 +186,9 @@ X-DBUS-ServiceName=org.kde.klipper X-KDE-UniqueApplet=true X-KDE-autostart-condition=klipperrc:General:AutoStart:false +# Tell AppStream generators not to merge info in this file so Discover and other +# software centers don't show two Klipper entries +X-AppStream-Ignore=true NotShowIn=KDE; Categories=Qt;KDE;Utility;X-KDE-Utilities-Desktop; Comment=A cut & paste history utility @@ -196,7 +199,7 @@ Comment[bg]=Инструмент за управление на операциите по копиране и поставяне Comment[bs]=Alatka za istorijat isecanja i naljepljivanja Comment[ca]=Una utilitat de l'historial per retallar i enganxar -Comment[ca@valencia]=Una utilitat de l'historial per retallar i enganxar +Comment[ca@valencia]=Una utilitat de l'historial per a retallar i apegar Comment[cs]=Nástroj pro historii práce se schránkou Comment[csb]=Nôrzãdze trzëmôjące historëjã tacnika Comment[da]=Et værktøj med historik til at klippe ud og indsætte @@ -229,7 +232,7 @@ Comment[km]=កាត់ និង​បិទភ្ជាប់​ឧបករណ៍​ប្រើប្រាស់​ប្រវត្តិ Comment[kn]=ಕತ್ತರಿಸು ಮತ್ತು ಅಂಟಿಸು ಚರಿತ್ರೆ ಸೌಲಭ್ಯ Comment[ko]=자르고 붙인 기록 도구 -Comment[lt]=„Iškirpti ir padėti“ istorijos pagalbinė programa +Comment[lt]=Iškirpimo ir įdėjimo istorijos paslaugų programa Comment[lv]=Izgriešanas un ielīmēšanas vēstures rīks Comment[mai]=काटू आओर साटू इतिहास यूटिलिटी Comment[mk]=Алатка за историјат на сечење и вметнување @@ -257,7 +260,6 @@ Comment[sv]=Ett verktyg med historik för klipp ut och klistra in Comment[ta]=A cut & paste history utility Comment[te]=కత్తిరించు & అతికించు చరిత్ర సౌలభ్యం -Comment[tg]=История буфера обмена Comment[th]=เครื่องมือดูประวัติการตัดและวาง Comment[tr]=Bir kes & yapıştır geçmişi aracı Comment[ug]=كەس ۋە چاپلا تارىخىنى باشقۇرۇش قورالى diff --git a/klipper/plasma-dataengine-clipboard.desktop b/klipper/plasma-dataengine-clipboard.desktop --- a/klipper/plasma-dataengine-clipboard.desktop +++ b/klipper/plasma-dataengine-clipboard.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Clipboard Name[ar]=الحافظة +Name[ast]=Cartafueyu Name[bs]=Klipbord Name[ca]=Porta-retalls Name[ca@valencia]=Porta-retalls @@ -23,6 +24,7 @@ Name[ja]=クリップボード Name[ko]=클립보드 Name[lt]=Iškarpinė +Name[lv]=Starpliktuve Name[nb]=Utklippstavle Name[nds]=Twischenaflaag Name[nl]=Klembord @@ -39,6 +41,7 @@ Name[sr@ijekavianlatin]=klipbord Name[sr@latin]=klipbord Name[sv]=Klippbord +Name[tg]=Ҳофизаи муваққатӣ Name[tr]=Pano Name[uk]=Буфер обміну Name[x-test]=xxClipboardxx @@ -67,6 +70,7 @@ Comment[ja]=クリップボードツール Comment[ko]=클립보드 도구. Comment[lt]=Iškarpinės įrankis. +Comment[lv]=Starpliktuves rīks. Comment[nb]=Utklippstavle-verktøy. Comment[nds]=Twischenaflaag-Warktüüch Comment[nl]=Klembordbeheer. @@ -83,6 +87,7 @@ Comment[sr@ijekavianlatin]=Alatka za klipbord Comment[sr@latin]=Alatka za klipbord Comment[sv]=Klippbordsverktyg. +Comment[tg]=Абзори ҳофизаи муваққатӣ. Comment[tr]=Pano Aracı. Comment[uk]=Утиліта буфера даних. Comment[x-test]=xxClipboard Tool.xx diff --git a/klipper/popupproxy.cpp b/klipper/popupproxy.cpp --- a/klipper/popupproxy.cpp +++ b/klipper/popupproxy.cpp @@ -117,7 +117,7 @@ // It would be much easier to use QMenu::initStyleOptions. But that is protected, so until we have a better // excuse to subclass that, I'd rather implement this manually. // Note 2 properties, tabwidth and maxIconWidth, are not available from the public interface, so those are left out (probably not - // important for height. Also, Exlsive checkType is disregarded as I don't think we will ever use it) + // important for height. Also, Exclusive checkType is disregarded as I don't think we will ever use it) style_options.initFrom(m_proxy_for_menu); style_options.checkType = action->isCheckable() ? QStyleOptionMenuItem::NonExclusive : QStyleOptionMenuItem::NotCheckable; style_options.checked = action->isChecked(); diff --git a/klipper/urlgrabber.cpp b/klipper/urlgrabber.cpp --- a/klipper/urlgrabber.cpp +++ b/klipper/urlgrabber.cpp @@ -23,14 +23,14 @@ #include "klipper_debug.h" #include #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -102,8 +102,7 @@ void URLGrabber::matchingMimeActions(const QString& clipData) { QUrl url(clipData); - KConfigGroup cg(KSharedConfig::openConfig(), "Actions"); - if(!cg.readEntry("EnableMagicMimeActions",true)) { + if(!KlipperSettings::enableMagicMimeActions()) { //qCDebug(KLIPPER_LOG) << "skipping mime magic due to configuration"; return; } @@ -393,11 +392,7 @@ QString appName = command.section( QLatin1Char(' '), 0, 0 ); if ( !appName.isEmpty() ) { - QPixmap iconPix = KIconLoader::global()->loadIcon( - appName, KIconLoader::Small, 0, - KIconLoader::DefaultState, - QStringList(), nullptr, true /* canReturnNull */ ); - if ( !iconPix.isNull() ) + if (QIcon::hasThemeIcon(appName)) icon = appName; else icon.clear(); diff --git a/krunner/CMakeLists.txt b/krunner/CMakeLists.txt --- a/krunner/CMakeLists.txt +++ b/krunner/CMakeLists.txt @@ -15,6 +15,7 @@ target_link_libraries(krunner Qt5::Quick + Qt5::Widgets KF5::Declarative KF5::I18n KF5::PlasmaQuick @@ -28,7 +29,9 @@ install(TARGETS krunner ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${krunner_dbusAppXML} DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) -install(FILES krunner.desktop DESTINATION ${DATA_INSTALL_DIR}/kglobalaccel) + +configure_file(krunner.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/krunner.desktop @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/krunner.desktop DESTINATION ${DATA_INSTALL_DIR}/kglobalaccel) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KRunnerAppDBusInterface") configure_package_config_file(KRunnerAppDBusInterfaceConfig.cmake.in diff --git a/krunner/krunner.desktop b/krunner/krunner.desktop.cmake rename from krunner/krunner.desktop rename to krunner/krunner.desktop.cmake --- a/krunner/krunner.desktop +++ b/krunner/krunner.desktop.cmake @@ -1,7 +1,8 @@ [Desktop Entry] -Exec=krunner +Exec=@CMAKE_INSTALL_PREFIX@/bin/krunner Name=KRunner Name[ar]=مشغّل.ك +Name[ast]=KRunner Name[bs]=KPokretač Name[ca]=KRunner Name[ca@valencia]=KRunner @@ -51,24 +52,38 @@ X-KDE-StartupNotify=false X-KDE-Shortcuts=Alt+Space,Alt+F2,Search Actions=RunClipboard +X-KDE-Wayland-Interfaces=org_kde_plasma_window_management [Desktop Action RunClipboard] Exec=krunner -c Name=Run command on clipboard contents Name[ca]=Executa una ordre al contingut del porta-retalls -Name[ca@valencia]=Executa una ordre al contingut del porta-retalls +Name[ca@valencia]=Executa una ordre en el contingut del porta-retalls +Name[cs]=Spustit příkaz nad obsahem schránky +Name[da]=Kør kommando på udklipsholderindholdet +Name[de]=Befehl mit dem Inhalt der Zwischenablage ausführen +Name[en_GB]=Run command on clipboard contents Name[es]=Ejecutar orden sobre el contenido del portapapeles +Name[et]=Käsu käivitamine lõikepuhvri sisuga Name[eu]=Exekutatu komandoa arbelaren edukiaren gain +Name[fi]=Suorita komento leikepöydän sisällöstä +Name[fr]=Exécuter la commande en utilisant le contenu du presse-papier +Name[gl]=Executar a orde segundo o contido do portapapeis +Name[hu]=Parancs futtatása a vágólap tartalmával Name[id]=Jalankan perintah pada konten-konten papan-klip Name[it]=Esegui comando sui contenuti degli appunti Name[ko]=클립보드 내용에서 명령 실행하기 +Name[lt]=Vykdyti komandas su iškarpinės turiniu Name[nl]=Commando uitvoeren op klembordinhoud Name[nn]=Køyr kommando på innhaldet på utklippstavla Name[pl]=Wykonaj polecenie na zawartości schowka Name[pt]=Executar o comando no conteúdo da área de transferência Name[pt_BR]=Executar comando no conteúdo da área de transferência +Name[ru]=Запуск команды из буфера обмена +Name[sk]=Spustiť príkaz na obsahu schránky Name[sv]=Kör kommando med klippbordets innehåll Name[uk]=Виконати команду над вмістом буфера обміну Name[x-test]=xxRun command on clipboard contentsxx +Name[zh_CN]=在剪贴板内容上运行命令 Name[zh_TW]=執行剪貼簿內容中的指令 X-KDE-Shortcuts=Alt+Shift+F2 diff --git a/krunner/main.cpp b/krunner/main.cpp --- a/krunner/main.cpp +++ b/krunner/main.cpp @@ -85,19 +85,11 @@ parser.process(app); aboutData.processCommandLine(&parser); - if (parser.isSet(replaceOption)) { - auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.krunner"), - QStringLiteral("/MainApplication"), - QStringLiteral("org.qtproject.Qt.QCoreApplication"), - QStringLiteral("quit")); - QDBusConnection::sessionBus().call(message); //deliberately block until it's done, so we register the name after the app quits - } - if (!KAuthorized::authorize(QStringLiteral("run_command"))) { return -1; } - KDBusService service(KDBusService::Unique); + KDBusService service(KDBusService::Unique | KDBusService::StartupOption(parser.isSet(replaceOption) ? KDBusService::Replace : 0)); QGuiApplication::setFallbackSessionManagementEnabled(false); diff --git a/krunner/view.cpp b/krunner/view.cpp --- a/krunner/view.cpp +++ b/krunner/view.cpp @@ -56,7 +56,7 @@ setColor(QColor(Qt::transparent)); setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); - KCrash::setFlags(KCrash::AutoRestart); + KCrash::initialize(); //used only by screen readers setTitle(i18n("KRunner")); @@ -90,13 +90,15 @@ } }; - auto screenAdded = [this](QScreen* screen) { + auto screenAdded = [this](const QScreen* screen) { connect(screen, &QScreen::geometryChanged, this, &View::screenGeometryChanged); screenGeometryChanged(); }; - foreach(QScreen* s, QGuiApplication::screens()) + const auto screens = QGuiApplication::screens(); + for(QScreen* s : screens) { screenAdded(s); + } connect(qGuiApp, &QGuiApplication::screenAdded, this, screenAdded); connect(qGuiApp, &QGuiApplication::screenRemoved, this, screenRemoved); @@ -125,8 +127,9 @@ void View::objectIncubated() { - connect(m_qmlObj->rootObject(), SIGNAL(widthChanged()), this, SLOT(resetScreenPos())); - setMainItem(qobject_cast(m_qmlObj->rootObject())); + auto mainItem = qobject_cast(m_qmlObj->rootObject()); + connect(mainItem, &QQuickItem::widthChanged, this, &View::resetScreenPos); + setMainItem(mainItem); } void View::slotFocusWindowChanged() @@ -218,7 +221,8 @@ { QScreen *shownOnScreen = QGuiApplication::primaryScreen(); - Q_FOREACH (QScreen* screen, QGuiApplication::screens()) { + const auto screens = QGuiApplication::screens(); + for (QScreen* screen : screens) { if (screen->geometry().contains(QCursor::pos(screen))) { shownOnScreen = screen; break; diff --git a/ksmserver/CMakeLists.txt b/ksmserver/CMakeLists.txt --- a/ksmserver/CMakeLists.txt +++ b/ksmserver/CMakeLists.txt @@ -1,7 +1,5 @@ add_definitions(-DTRANSLATION_DOMAIN=\"ksmserver\") -include_directories(${PHONON_INCLUDE_DIR}) - check_library_exists(ICE _IceTransNoListen "" HAVE__ICETRANSNOLISTEN) configure_file(config-ksmserver.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ksmserver.h) @@ -25,6 +23,8 @@ qt5_add_dbus_adaptor( ksmserver_KDEINIT_SRCS org.kde.KSMServerInterface.xml server.h KSMServer ) qt5_add_dbus_interface( ksmserver_KDEINIT_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/org.kde.screensaver.xml kscreenlocker_interface ) qt5_add_dbus_interface( ksmserver_KDEINIT_SRCS org.kde.LogoutPrompt.xml logoutprompt_interface) +qt5_add_dbus_interface( ksmserver_KDEINIT_SRCS org.kde.KWin.Session.xml kwinsession_interface) + set(klauncher_xml ${KINIT_DBUS_INTERFACES_DIR}/kf5_org.kde.KLauncher.xml) qt5_add_dbus_interface( ksmserver_KDEINIT_SRCS ${klauncher_xml} klauncher_interface ) @@ -59,7 +59,6 @@ KF5::Notifications KF5::Package KF5::WindowSystem - ${PHONON_LIBRARIES} Qt5::Concurrent ) diff --git a/ksmserver/legacy.cpp b/ksmserver/legacy.cpp --- a/ksmserver/legacy.cpp +++ b/ksmserver/legacy.cpp @@ -31,6 +31,7 @@ #include #include +#include #include @@ -168,7 +169,7 @@ } // Wait for change in WM_COMMAND with timeout XFlush(newdisplay); - QTime start = QTime::currentTime(); + QElapsedTimer start; while (awaiting_replies > 0) { if (XPending(newdisplay)) { /* Process pending event */ @@ -253,7 +254,7 @@ if( config->hasGroup( QStringLiteral( "Legacy" ) + sessionGroup )) { KConfigGroup group( config, QStringLiteral( "Legacy" ) + sessionGroup ); restoreLegacySessionInternal( &group ); - } else if( wm == QStringLiteral( "kwin" ) ) { // backwards comp. - get it from kwinrc + } else if( wm == QLatin1String( "kwin" ) ) { // backwards comp. - get it from kwinrc KConfigGroup group( config, sessionGroup ); int count = group.readEntry( "count", 0 ); for ( int i = 1; i <= count; i++ ) { @@ -265,7 +266,7 @@ for( QStringList::ConstIterator it = restartCommand.constBegin(); it != restartCommand.constEnd(); ++it ) { - if( (*it) == QStringLiteral( "-session" ) ) { + if( (*it) == QLatin1String( "-session" ) ) { ++it; if( it != restartCommand.constEnd()) { KConfig cfg( QStringLiteral( "session/" ) + wm + @@ -349,15 +350,15 @@ // Mozilla is launched using wrapper scripts, so it's launched using "mozilla", // but the actual binary is "mozilla-bin" or "/mozilla-bin", and that's what // will be also in WM_COMMAND - using this "mozilla-bin" doesn't work at all though - if( command.endsWith( QStringLiteral( "mozilla-bin" ))) + if( command.endsWith(QLatin1String( "mozilla-bin" ))) return QStringList() << QStringLiteral( "mozilla" ); - if( command.endsWith( QStringLiteral( "firefox-bin" ))) + if( command.endsWith(QLatin1String( "firefox-bin" ))) return QStringList() << QStringLiteral( "firefox" ); - if( command.endsWith( QStringLiteral( "thunderbird-bin" ))) + if( command.endsWith(QLatin1String( "thunderbird-bin" ))) return QStringList() << QStringLiteral( "thunderbird" ); - if( command.endsWith( QStringLiteral( "sunbird-bin" ))) + if( command.endsWith(QLatin1String( "sunbird-bin" ))) return QStringList() << QStringLiteral( "sunbird" ); - if( command.endsWith( QStringLiteral( "seamonkey-bin" ))) + if( command.endsWith(QLatin1String( "seamonkey-bin" ))) return QStringList() << QStringLiteral( "seamonkey" ); } return ret; diff --git a/ksmserver/logout.cpp b/ksmserver/logout.cpp --- a/ksmserver/logout.cpp +++ b/ksmserver/logout.cpp @@ -58,12 +58,11 @@ #endif #include -#include #include #include -#include #include #include +#include #include #include @@ -78,11 +77,13 @@ #include "logoutprompt_interface.h" #include "shutdown_interface.h" +#include "kwinsession_interface.h" -#include -#include -#include -#include +enum KWinSessionState { + Normal = 0, + Saving = 1, + Quitting = 2 +}; void KSMServer::logout( int confirm, int sdtype, int sdmode ) { @@ -189,13 +190,18 @@ if (state != Idle) { QTimer::singleShot(1000, this, &KSMServer::performLogout); } + + auto reply = m_kwinInterface->setState(KWinSessionState::Saving); + // we don't need to block as we wait for kwin to handle it's session 1 + // before messaging the clients + state = Shutdown; // shall we save the session on logout? KConfigGroup cg(KSharedConfig::openConfig(), "General"); saveSession = ( cg.readEntry( "loginMode", QStringLiteral( "restorePreviousLogout" ) ) - == QStringLiteral( "restorePreviousLogout" ) ); + == QLatin1String( "restorePreviousLogout" ) ); qCDebug(KSMSERVER) << "saveSession is " << saveSession; @@ -254,9 +260,10 @@ return; if ( currentSession().isEmpty() || currentSession() == QString::fromLocal8Bit( SESSION_PREVIOUS_LOGOUT ) ) - sessionGroup = QStringLiteral("Session: ") + QString::fromLocal8Bit( SESSION_BY_USER ); + sessionGroup = QLatin1String("Session: ") + QString::fromLocal8Bit( SESSION_BY_USER ); state = Checkpoint; + wmPhase1WaitingCount = 0; saveType = SmSaveLocal; saveSession = true; @@ -411,6 +418,9 @@ } } state = Idle; + + m_kwinInterface->setState(KWinSessionState::Normal); + if (m_performLogoutCall.type() == QDBusMessage::MethodCallMessage) { auto reply = m_performLogoutCall.createReply(false); QDBusConnection::sessionBus().send(reply); @@ -505,8 +515,6 @@ startKilling(); } }); - createLogoutEffectWidget(); - } else if ( state == Checkpoint ) { foreach( KSMClient* c, clients ) { SmsSaveComplete( c->connection()); @@ -527,6 +535,9 @@ } // kill all clients state = Killing; + + m_kwinInterface->setState(KWinSessionState::Quitting); + foreach( KSMClient* c, clients ) { if( isWM( c )) // kill the WM as the last one in order to reduce flicker continue; @@ -561,7 +572,6 @@ { if( state != Killing ) return; - delete logoutEffectWidget; qCDebug(KSMSERVER) << "Starting killing WM"; state = KillingWM; @@ -618,25 +628,6 @@ killingCompleted(); } -void KSMServer::createLogoutEffectWidget() -{ -// Ok, this is rather a hack. In order to fade the whole desktop when playing the logout -// sound, killing applications and leaving KDE, create a dummy window that triggers -// the logout fade effect again. - logoutEffectWidget = new QWidget( nullptr, Qt::X11BypassWindowManagerHint ); - logoutEffectWidget->winId(); // workaround for Qt4.3 setWindowRole() assert - logoutEffectWidget->setWindowRole( QStringLiteral( "logouteffect" ) ); - - // Qt doesn't set this on unmanaged windows - //FIXME: or does it? - XChangeProperty( QX11Info::display(), logoutEffectWidget->winId(), - XInternAtom( QX11Info::display(), "WM_WINDOW_ROLE", False ), XA_STRING, 8, PropModeReplace, - (unsigned char *)"logouteffect", strlen( "logouteffect" )); - - logoutEffectWidget->setGeometry( -100, -100, 1, 1 ); - logoutEffectWidget->show(); -} - void KSMServer::saveSubSession(const QString &name, QStringList saveAndClose, QStringList saveOnly) { if( state != Idle ) { // performing startup diff --git a/ksmserver/main.cpp b/ksmserver/main.cpp --- a/ksmserver/main.cpp +++ b/ksmserver/main.cpp @@ -195,7 +195,7 @@ if (!writeTest(path)) { if (errno == ENOSPC) - msg = i18n("Temp directory (%1) is out of disk space."); + msg = i18n("Temp directory (%1) is out of disk space.", QFile::decodeName(path)); else msg = i18n("Writing to the temp directory (%2) failed with\n " "the error '%1'", QString::fromLocal8Bit(strerror(errno)), QFile::decodeName(path)); @@ -205,9 +205,9 @@ { path += "/.ICE-unix"; if (access(path.data(), W_OK) && (errno != ENOENT)) - msg = i18n("No write access to '%1'."); + msg = i18n("No write access to '%1'.", QFile::decodeName(path)); else if (access(path.data(), R_OK) && (errno != ENOENT)) - msg = i18n("No read access to '%1'."); + msg = i18n("No read access to '%1'.", QFile::decodeName(path)); } if (!msg.isEmpty()) { @@ -324,9 +324,9 @@ if ( parser.isSet( restoreOption )) server->restoreSession( QStringLiteral( SESSION_BY_USER ) ); - else if ( loginMode == QStringLiteral( "restorePreviousLogout" ) ) + else if ( loginMode == QLatin1String( "restorePreviousLogout" ) ) server->restoreSession( QStringLiteral( SESSION_PREVIOUS_LOGOUT ) ); - else if ( loginMode == QStringLiteral( "restoreSavedSession" ) ) + else if ( loginMode == QLatin1String( "restoreSavedSession" ) ) server->restoreSession( QStringLiteral( SESSION_BY_USER ) ); else server->startDefaultSession(); diff --git a/ksmserver/org.kde.KWin.Session.xml b/ksmserver/org.kde.KWin.Session.xml new file mode 100644 --- /dev/null +++ b/ksmserver/org.kde.KWin.Session.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/ksmserver/server.h b/ksmserver/server.h --- a/ksmserver/server.h +++ b/ksmserver/server.h @@ -62,6 +62,7 @@ class KSMConnection; class KSMClient; +class OrgKdeKWinSessionInterface; enum SMType { SM_ERROR, SM_WMCOMMAND, SM_WMSAVEYOURSELF }; struct SMData @@ -83,6 +84,7 @@ ImmediateLockScreen = 1 << 1, NoLockScreen = 1 << 2 }; + Q_DECLARE_FLAGS(InitFlags, InitFlag) KSMServer( const QString& windowManager, InitFlags flags ); ~KSMServer() override; @@ -155,7 +157,6 @@ void completeKillingWM(); void cancelShutdown( KSMClient* c ); void killingCompleted(); - void createLogoutEffectWidget(); void discardSession(); void storeSession(); @@ -240,7 +241,6 @@ QTimer protectionTimer; QTimer restoreTimer; QString xonCommand; - QWidget* logoutEffectWidget; // sequential startup int appsToStart; int lastAppStarted; @@ -257,6 +257,8 @@ QList clientsToKill; QList clientsToSave; + OrgKdeKWinSessionInterface *m_kwinInterface; + int sockets[2]; friend bool readFromPipe(int pipe); }; diff --git a/ksmserver/server.cpp b/ksmserver/server.cpp --- a/ksmserver/server.cpp +++ b/ksmserver/server.cpp @@ -93,6 +93,7 @@ #include #include "kscreenlocker_interface.h" +#include "kwinsession_interface.h" KSMServer* the_server = nullptr; @@ -119,7 +120,7 @@ command.prepend( QStandardPaths::findExecutable(QStringLiteral("kdesu"))); } } - if ( !clientMachine.isEmpty() && clientMachine != QStringLiteral("localhost") ) { + if ( !clientMachine.isEmpty() && clientMachine != QLatin1String("localhost") ) { command.prepend( clientMachine ); command.prepend( xonCommand ); // "xon" by default } @@ -599,7 +600,7 @@ KSMServer::KSMServer( const QString& windowManager, InitFlags flags ) : wmProcess( nullptr ) , sessionGroup( QStringLiteral( "" ) ) - , logoutEffectWidget( nullptr ) + , m_kwinInterface(new OrgKdeKWinSessionInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Session"), QDBusConnection::sessionBus(), this)) , sockets{ -1, -1 } { if (!flags.testFlag(InitFlag::NoLockScreen)) { @@ -1036,7 +1037,7 @@ qCDebug(KSMSERVER) << "KSMServer::restoreSession " << sessionName; KSharedConfig::Ptr config = KSharedConfig::openConfig(); - sessionGroup = QStringLiteral("Session: ") + sessionName; + sessionGroup = QLatin1String("Session: ") + sessionName; KConfigGroup configSessionGroup( config, sessionGroup); int count = configSessionGroup.readEntry( "count", 0 ); @@ -1083,7 +1084,7 @@ if( wmProcess->state() == QProcess::NotRunning ) { // wm failed to launch for some reason, go with kwin instead qCWarning(KSMSERVER) << "Window manager" << wm << "failed to launch"; - if( wm == QStringLiteral( KWIN_BIN ) ) + if( wm == QLatin1String( KWIN_BIN ) ) return; // uhoh, kwin itself failed qCDebug(KSMSERVER) << "Launching KWin"; wm = QStringLiteral( KWIN_BIN ); @@ -1171,7 +1172,7 @@ while ( lastAppStarted < appsToStart ) { lastAppStarted++; QString n = QString::number(lastAppStarted); - QString clientId = config.readEntry( QStringLiteral("clientId")+n, QString() ); + QString clientId = config.readEntry( QLatin1String("clientId")+n, QString() ); bool alreadyStarted = false; foreach ( KSMClient *c, clients ) { if ( QString::fromLocal8Bit( c->clientId() ) == clientId ) { @@ -1182,7 +1183,7 @@ if ( alreadyStarted ) continue; - QStringList restartCommand = config.readEntry( QStringLiteral("restartCommand")+n, QStringList() ); + QStringList restartCommand = config.readEntry( QLatin1String("restartCommand")+n, QStringList() ); if ( restartCommand.isEmpty() || (config.readEntry( QStringLiteral("restartStyleHint")+n, 0 ) == SmRestartNever)) { continue; diff --git a/ksplash/ksplashqml/CMakeLists.txt b/ksplash/ksplashqml/CMakeLists.txt --- a/ksplash/ksplashqml/CMakeLists.txt +++ b/ksplash/ksplashqml/CMakeLists.txt @@ -16,6 +16,7 @@ KF5::QuickAddons KF5::WaylandClient KF5::WindowSystem + KF5::QuickAddons PW::KWorkspace ) diff --git a/ksplash/ksplashqml/main.cpp b/ksplash/ksplashqml/main.cpp --- a/ksplash/ksplashqml/main.cpp +++ b/ksplash/ksplashqml/main.cpp @@ -23,6 +23,8 @@ #include +#include + #include #include @@ -63,7 +65,7 @@ test = true; } - + KQuickAddons::QtQuickSettings::init(); //enable to send log output to /tmp/ksplash //which is useful for debugging // qInstallMsgHandler(myMessageHandler); diff --git a/ksplash/ksplashqml/themes/Minimalistic/Theme.rc b/ksplash/ksplashqml/themes/Minimalistic/Theme.rc --- a/ksplash/ksplashqml/themes/Minimalistic/Theme.rc +++ b/ksplash/ksplashqml/themes/Minimalistic/Theme.rc @@ -3,7 +3,7 @@ Description = Animated KDE logo on a black background Version = 1.0 Author = Ivan Cukic -Homepage = http://www.kde.org +Homepage = https://www.kde.org # Theme behaviour settings. Engine = KSplashQML diff --git a/ktimezoned/ktimezoned.desktop b/ktimezoned/ktimezoned.desktop --- a/ktimezoned/ktimezoned.desktop +++ b/ktimezoned/ktimezoned.desktop @@ -8,6 +8,7 @@ Name=Time Zone Name[ar]=المنطقة الزمنية +Name[ast]=Fusu horariu Name[bg]=Часови пояс Name[bn]=টাইম জোন Name[bs]=Vremenska zona @@ -94,7 +95,7 @@ Comment[fi]=Tarjoaa järjestelmän aikavyöhyketiedon ohjelmille Comment[fr]=Fournit le fuseau horaire du système aux applications Comment[ga]=Soláthraíonn sé crios ama an chórais d'fheidhmchláir -Comment[gl]=Fornece o fuso horario do sistema aos aplicativos +Comment[gl]=Fornece o fuso horario do sistema ás aplicacións Comment[gu]=સિસ્ટમનો સમય વિસ્તાર કાર્યક્રમોને પૂરો પાડે છે Comment[he]=מספק את אזור הזמן של המערכת עבור יישומים Comment[hi]=अनुप्रयोगों को तंत्र का समय क्षेत्र उपलब्ध कराता है @@ -109,7 +110,7 @@ Comment[km]=ផ្ដល់​តំបន់​ពេលវេលា​​ប្រព័ន្ធ​ឲ្យ​​កម្មវិធី Comment[kn]=ವ್ಯವಸ್ಥೆಯ ಕಾಲವಲಯವನ್ನು ಅನ್ವಯಗಳಿಗೆ ಒದಗಿಸುತ್ತದೆ Comment[ko]=프로그램에 시스템 시간대 정보 제공 -Comment[lt]=Nurodo programoms sistemos laiko juostą +Comment[lt]=Pateikia programoms sistemos laiko juostą Comment[lv]=Sniedz informāciju par pašreizējo laika joslu dažādām programmām Comment[ml]=സിസ്റ്റത്തിലെ സമയമേഖല പ്രയോഗങ്ങള്‍ക്കു് നല്‍കുന്നു Comment[mr]=अनुप्रयोगांस प्रणालीचे वेळ क्षेत्र पुरवितो @@ -131,7 +132,6 @@ Comment[sr@ijekavianlatin]=Ispostavlja programima sistemsku vremensku zonu Comment[sr@latin]=Ispostavlja programima sistemsku vremensku zonu Comment[sv]=Tillhandahåller systemets tidszon till program -Comment[tg]=Минтақаи вақти системаро барои барномаҳо таъмин мекунад Comment[th]=กำหนดเขตเวลาของระบบให้กับโปรแกรมต่าง ๆ Comment[tr]=Sistem zaman dilimini uygulamalara sunar Comment[ug]=پروگراممىغا سىستېما ۋاقىت رايون ئۇچۇرى تەمىنلەيدۇ diff --git a/ktimezoned/ktimezoned_win.cpp b/ktimezoned/ktimezoned_win.cpp --- a/ktimezoned/ktimezoned_win.cpp +++ b/ktimezoned/ktimezoned_win.cpp @@ -65,7 +65,7 @@ void run() { -//FIXME: the timezonechange needs to be handled diffrently +//FIXME: the timezonechange needs to be handled differently #ifndef _WIN32 if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, currentTimeZoneKey, 0, KEY_READ, &key ) == ERROR_SUCCESS ) { diff --git a/libcolorcorrect/autotests/nightcolortest.cpp b/libcolorcorrect/autotests/nightcolortest.cpp --- a/libcolorcorrect/autotests/nightcolortest.cpp +++ b/libcolorcorrect/autotests/nightcolortest.cpp @@ -155,7 +155,7 @@ QCOMPARE(aptr->checkStaged(), isChange); QCOMPARE(aptr->checkStagedAll(), isChangeAll); - QSignalSpy *dataUpdateSpy = new QSignalSpy(aptr, SIGNAL(dataUpdated())); + QSignalSpy *dataUpdateSpy = new QSignalSpy(aptr, &CompositorAdaptor::dataUpdated); QVERIFY(dataUpdateSpy->isValid()); // send config relative to active and mode state @@ -169,7 +169,7 @@ setCompBackToDefault(); delete aptr; aptr = new CompositorAdaptor(this); - dataUpdateSpy = new QSignalSpy(aptr, SIGNAL(dataUpdated())); + dataUpdateSpy = new QSignalSpy(aptr, &CompositorAdaptor::dataUpdated); QVERIFY(dataUpdateSpy->isValid()); QVERIFY(!aptr->checkStaged()); @@ -192,7 +192,7 @@ setCompBackToDefault(); CompositorAdaptor *aptr = new CompositorAdaptor(this); - QSignalSpy *dataUpdateSpy = new QSignalSpy(aptr, SIGNAL(dataUpdated())); + QSignalSpy *dataUpdateSpy = new QSignalSpy(aptr, &CompositorAdaptor::dataUpdated); QVERIFY(dataUpdateSpy->isValid()); aptr->sendAutoLocationUpdate(10, 20); diff --git a/libcolorcorrect/compositorcoloradaptor.h b/libcolorcorrect/compositorcoloradaptor.h --- a/libcolorcorrect/compositorcoloradaptor.h +++ b/libcolorcorrect/compositorcoloradaptor.h @@ -97,7 +97,8 @@ enum class Mode { ModeAutomatic, ModeLocation, - ModeTimings + ModeTimings, + ModeConstant, }; Q_ENUMS(Mode) @@ -160,7 +161,7 @@ return (int)m_modeStaged; } void setModeStaged(int mode) { - if (mode < 0 || 2 < mode || (int)m_modeStaged == mode) { + if (mode < 0 || 3 < mode || (int)m_modeStaged == mode) { return; } m_modeStaged = (Mode)mode; diff --git a/libcolorcorrect/compositorcoloradaptor.cpp b/libcolorcorrect/compositorcoloradaptor.cpp --- a/libcolorcorrect/compositorcoloradaptor.cpp +++ b/libcolorcorrect/compositorcoloradaptor.cpp @@ -259,6 +259,8 @@ m_morningBeginFixed != m_morningBeginFixedStaged || m_eveningBeginFixed != m_eveningBeginFixedStaged || m_transitionTime != m_transitionTimeStaged; + case Mode::ModeConstant: + return baseDataChange; default: // never reached return false; diff --git a/libcolorcorrect/kded/colorcorrectlocationupdater.desktop b/libcolorcorrect/kded/colorcorrectlocationupdater.desktop --- a/libcolorcorrect/kded/colorcorrectlocationupdater.desktop +++ b/libcolorcorrect/kded/colorcorrectlocationupdater.desktop @@ -2,17 +2,21 @@ Type=Service Name=ColorCorrect Geolocation Updater Name[ca]=Actualitzador de la geolocalització del ColorCorrect -Name[ca@valencia]=Actualitzador de la geolocalització del ColorCorrect +Name[ca@valencia]=Actualitzador de la geolocalització de ColorCorrect Name[da]=Opdatering af geolokalisering til ColorCorrect Geolocation Name[de]=Geolokalisierung-Aktualisierung zur Farbkorrektur Name[en_GB]=ColourCorrect Geolocation Updater Name[es]=Actualizador de geolocalización ColorCorrect +Name[et]=ColorCorrecti geolokatsiooni uuendaja Name[eu]=ColorCorrect geokokapen eguneratzailea +Name[fi]=ColorCorrect Geolocation -päivittäjä Name[fr]=Actualisation de géolocalisation ColorCorrect Name[gl]=Actualizador de geolocalización de ColorCorrect -Name[id]=Pengupdate Geolokasi ColorCorrect +Name[hu]=ColorCorrect geolokáció-frissítő +Name[id]=Pembaru Geolokasi ColorCorrect Name[it]=ColorCorrect Geolocation Updater Name[ko]=ColorCorrect 위치 업데이터 +Name[lt]=ColorCorrect geografinės vietos nustatymo atnaujinimo įrankis Name[nl]=Bijwerker van kleurgecorrigeerde geo-locatie Name[nn]=ColorCorrect geolokasjonsoppdaterar Name[pl]=Uaktualnianie położenia geograficznego dla poprawnej barwy @@ -43,13 +47,16 @@ Comment[el]=Στέλνει ενημερωμένα δεδομένα τοποθεσίας στον συνθέτη Comment[en_GB]=Sends updated location data to the compositor Comment[es]=Envía datos de posicionamiento actualizados al compositor +Comment[et]=Asukohaandmete saatmine komposiidihaldurile Comment[eu]=kokapen datu eguneratuak bidaltzen dizkio konposatzaileari Comment[fi]=Lähettää päivitetyn sijaintitiedon koostajalle Comment[fr]=Envoie des données de localisation actualisées au compositeur Comment[gl]=Envía datos de localización actualizados ao compositor. -Comment[id]=Mengirimkan data lokasi yang diupdate ke kompositor +Comment[hu]=Frissített lokációs adatok küldése a kompozitornak +Comment[id]=Mengirimkan data lokasi yang diperbarui ke kompositor Comment[it]=Invia i dati di posizione aggiornati al compositore Comment[ko]=컴포지터에 업데이트된 위치 데이터 보내기 +Comment[lt]=Siunčia atnaujintus vietos duomenis į kompozitorių Comment[nl]=Verstuurt bijgewerkte locatiegegevens naar de compositeur Comment[nn]=Send oppdater staddata til samansetjaren Comment[pl]=Wysyła uaktualnione dane położenia do kompozytora diff --git a/libcolorcorrect/suncalc.cpp b/libcolorcorrect/suncalc.cpp --- a/libcolorcorrect/suncalc.cpp +++ b/libcolorcorrect/suncalc.cpp @@ -29,7 +29,7 @@ QVariantMap calculateSunTimings(double latitude, double longitude, bool morning) { - // calculations based on http://aa.quae.nl/en/reken/zonpositie.html + // calculations based on https://aa.quae.nl/en/reken/zonpositie.html // accuracy: +/- 5min // positioning diff --git a/libdbusmenuqt/dbusmenuimporter.cpp b/libdbusmenuqt/dbusmenuimporter.cpp --- a/libdbusmenuqt/dbusmenuimporter.cpp +++ b/libdbusmenuqt/dbusmenuimporter.cpp @@ -431,7 +431,7 @@ d->m_actionForId.remove(id); }); - connect(action, &QAction::triggered, this, [action, id, this]() { + connect(action, &QAction::triggered, this, [ id, this]() { sendClickedEvent(id); }); diff --git a/libkworkspace/kdisplaymanager.cpp b/libkworkspace/kdisplaymanager.cpp --- a/libkworkspace/kdisplaymanager.cpp +++ b/libkworkspace/kdisplaymanager.cpp @@ -377,7 +377,7 @@ * @param buf the result buffer. * @return result: * @li If true, the command was successfully executed. - * @p ret might contain addional results. + * @p ret might contain additional results. * @li If false and @p ret is empty, a communication error occurred * (most probably KDM is not running). * @li If false and @p ret is non-empty, it contains the error message diff --git a/libkworkspace/sessionmanagement.cpp b/libkworkspace/sessionmanagement.cpp --- a/libkworkspace/sessionmanagement.cpp +++ b/libkworkspace/sessionmanagement.cpp @@ -62,6 +62,8 @@ connect(backend, &SessionBackend::canSuspendChanged, this, &SessionManagement::canSuspendChanged); connect(backend, &SessionBackend::canHybridSuspendChanged, this, &SessionManagement::canHybridSuspendChanged); connect(backend, &SessionBackend::canHibernateChanged, this, &SessionManagement::canHibernateChanged); + connect(backend, &SessionBackend::aboutToSuspend, this, &SessionManagement::aboutToSuspend); + connect(backend, &SessionBackend::resumingFromSuspend, this, &SessionManagement::resumingFromSuspend); } bool SessionManagement::canShutdown() const diff --git a/libnotificationmanager/CMakeLists.txt b/libnotificationmanager/CMakeLists.txt --- a/libnotificationmanager/CMakeLists.txt +++ b/libnotificationmanager/CMakeLists.txt @@ -8,7 +8,9 @@ set(notificationmanager_LIB_SRCS server.cpp server_p.cpp + serverinfo.cpp settings.cpp + mirroredscreenstracker.cpp notifications.cpp notification.cpp @@ -33,11 +35,7 @@ IDENTIFIER NOTIFICATIONMANAGER CATEGORY_NAME org.kde.plasma.notifications) -if (${ECM_VERSION} STRGREATER "5.58.0") - install(FILES libnotificationmanager.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) -else() - install(FILES libnotificationmanager.categories DESTINATION ${KDE_INSTALL_CONFDIR}) -endif() +install(FILES libnotificationmanager.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) # Settings kconfig_add_kcfg_files(notificationmanager_LIB_SRCS kcfg/donotdisturbsettings.kcfgc) @@ -75,10 +73,9 @@ Qt5::DBus KF5::ConfigGui KF5::I18n - KF5::IconThemes KF5::KIOFileWidgets KF5::Plasma - KF5::ProcessCore + KF5::Screen KF5::Service ) diff --git a/libnotificationmanager/autotests/notifications_test.cpp b/libnotificationmanager/autotests/notifications_test.cpp --- a/libnotificationmanager/autotests/notifications_test.cpp +++ b/libnotificationmanager/autotests/notifications_test.cpp @@ -62,11 +62,11 @@ QTest::newRow("image remote URL") << "This is \"cheese\" and more text" << "This is \"cheese\"/ and more text"; - //more bad formatted options. To some extent actual output doesn't matter. Garbage in, garbabe out. + //more bad formatted options. To some extent actual output doesn't matter. Garbage in, garbage out. //the important thing is that it doesn't contain anything that could be parsed as the remote URL QTest::newRow("image remote URL no close") << "This is \" alt=\"cheese\"> and more text" << "This is \"cheese\" and more text"; QTest::newRow("image remote URL double open") << "This is <\" and more text" << "This is "; - QTest::newRow("image remote URL no entitiy close") << "This is \"cheese\" and more text" << "This is "; QTest::newRow("link") << "This is a link and more text" << "This is a link and more text"; diff --git a/libnotificationmanager/dbus/org.freedesktop.Notifications.xml b/libnotificationmanager/dbus/org.freedesktop.Notifications.xml --- a/libnotificationmanager/dbus/org.freedesktop.Notifications.xml +++ b/libnotificationmanager/dbus/org.freedesktop.Notifications.xml @@ -9,6 +9,11 @@ + + + + + diff --git a/libnotificationmanager/declarative/notificationmanagerplugin.cpp b/libnotificationmanager/declarative/notificationmanagerplugin.cpp --- a/libnotificationmanager/declarative/notificationmanagerplugin.cpp +++ b/libnotificationmanager/declarative/notificationmanagerplugin.cpp @@ -22,6 +22,8 @@ #include "notifications.h" #include "job.h" +#include "server.h" +#include "serverinfo.h" #include "settings.h" #include @@ -35,4 +37,9 @@ qmlRegisterType(uri, 1, 0, "Notifications"); qmlRegisterUncreatableType(uri, 1, 0, "Job", QStringLiteral("Can only access Job via JobDetailsRole of JobsModel")); qmlRegisterType(uri, 1, 0, "Settings"); + qmlRegisterSingletonType(uri, 1, 0, "Server", [](QQmlEngine *, QJSEngine *) -> QObject* { + QQmlEngine::setObjectOwnership(&Server::self(), QQmlEngine::CppOwnership); + return &Server::self(); + }); + qmlRegisterUncreatableType(uri, 1, 0, "ServerInfo", QStringLiteral("Can only access ServerInfo via Server")); } diff --git a/libnotificationmanager/job.cpp b/libnotificationmanager/job.cpp --- a/libnotificationmanager/job.cpp +++ b/libnotificationmanager/job.cpp @@ -33,7 +33,16 @@ : QObject(parent) , d(new JobPrivate(id, this)) { - d->m_created = QDateTime::currentDateTimeUtc(); + d->m_created = QDateTime::currentDateTimeUtc(); + + // These properties are used in generating the pretty job text + connect(d, &JobPrivate::infoMessageChanged, this, &Job::textChanged); + connect(this, &Job::processedFilesChanged, this, &Job::textChanged); + connect(this, &Job::totalFilesChanged, this, &Job::textChanged); + connect(this, &Job::descriptionValue1Changed, this, &Job::textChanged); + connect(this, &Job::descriptionValue2Changed, this, &Job::textChanged); + connect(this, &Job::destUrlChanged, this, &Job::textChanged); + connect(this, &Job::errorTextChanged, this, &Job::textChanged); } Job::~Job() = default; @@ -271,10 +280,10 @@ void Job::resume() { - emit d->resumeRequested();; + emit d->resumeRequested(); } void Job::kill() { - emit d->cancelRequested(); + d->kill(); } diff --git a/libnotificationmanager/job_p.h b/libnotificationmanager/job_p.h --- a/libnotificationmanager/job_p.h +++ b/libnotificationmanager/job_p.h @@ -32,6 +32,7 @@ #include "notifications.h" #include "job.h" +class QTimer; class KFilePlacesModel; namespace NotificationManager @@ -50,6 +51,8 @@ QUrl descriptionUrl() const; QString text() const; + void kill(); + // DBus // JobViewV1 void terminate(const QString &errorMessage); @@ -71,6 +74,8 @@ signals: void closed(); + void infoMessageChanged(); + // DBus // V1 and V2 void suspendRequested(); @@ -94,6 +99,19 @@ return false; } + template bool updateFieldFromProperties(const QVariantMap &properties, + const QString &keyName, + T &target, + void (Job::*changeSignal)()) + { + auto it = properties.find(keyName); + if (it == properties.end()) { + return false; + } + + return updateField(it->value(), target, changeSignal); + } + static QSharedPointer createPlacesModel(); static QUrl localFileOrUrl(const QString &stringUrl); @@ -103,13 +121,16 @@ void finish(); + QTimer *m_killTimer = nullptr; + uint m_id = 0; QDBusObjectPath m_objectPath; QDateTime m_created; QDateTime m_updated; QString m_summary; + QString m_infoMessage; QString m_desktopEntry; QString m_applicationName; diff --git a/libnotificationmanager/job_p.cpp b/libnotificationmanager/job_p.cpp --- a/libnotificationmanager/job_p.cpp +++ b/libnotificationmanager/job_p.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -37,14 +38,11 @@ #include "jobviewv2adaptor.h" #include "jobviewv3adaptor.h" -#include - using namespace NotificationManager; JobPrivate::JobPrivate(uint id, QObject *parent) : QObject(parent) , m_id(id) - , m_placesModel(createPlacesModel()) { m_objectPath.setPath(QStringLiteral("/org/kde/notificationmanager/jobs/JobView_%1").arg(id)); @@ -119,7 +117,7 @@ const QString homePath = QDir::homePath(); if (destUrlString.startsWith(homePath)) { - destUrlString = QStringLiteral("~") + destUrlString.mid(homePath.length()); + destUrlString = QLatin1String("~") + destUrlString.mid(homePath.length()); } return destUrlString; @@ -154,6 +152,10 @@ return m_errorText; } + if (!m_infoMessage.isEmpty()) { + return m_infoMessage; + } + const QString currentFileName = descriptionUrl().fileName(); const QString destUrlString = prettyDestUrl(); @@ -200,6 +202,28 @@ return QString(); } +void JobPrivate::kill() +{ + emit cancelRequested(); + + // In case the application doesn't respond, remove the job + if (!m_killTimer) { + m_killTimer = new QTimer(this); + m_killTimer->setSingleShot(true); + connect(m_killTimer, &QTimer::timeout, this, [this] { + qCWarning(NOTIFICATIONMANAGER) << "Application" << m_applicationName << "failed to respond to a cancel request in time"; + Job *job = static_cast(parent()); + job->setError(KIO::ERR_USER_CANCELED); + job->setState(Notifications::JobStateStopped); + finish(); + }); + } + + if (!m_killTimer->isActive()) { + m_killTimer->start(2000); + } +} + QUrl JobPrivate::descriptionUrl() const { QUrl url = localFileOrUrl(m_descriptionValue2); @@ -285,6 +309,8 @@ updateHasDetails(); } +// NOTE infoMessage isn't supposed to be the "Copying..." heading but e.g. a "Connecting to server..." status message +// JobViewV1/V2 got that wrong but JobView3 uses "title" and "infoMessage" correctly respectively. void JobPrivate::setInfoMessage(const QString &infoMessage) { updateField(infoMessage, m_summary, &Job::summaryChanged); @@ -338,7 +364,56 @@ void JobPrivate::update(const QVariantMap &properties) { - // TODO - sendErrorReply(QDBusError::NotSupported, QStringLiteral("JobViewV3 update is not yet implemented.")); - Q_UNUSED(properties) + auto end = properties.end(); + + auto it = properties.find(QStringLiteral("title")); + if (it != end) { + updateField(it->toString(), m_summary, &Job::summaryChanged); + } + + it = properties.find(QStringLiteral("infoMessage")); + if (it != end) { + // InfoMessage is exposed via text()/BodyRole, not via public API, hence no public signal + const QString infoMessage = it->toString(); + if (m_infoMessage != infoMessage) { + m_infoMessage = it->toString(); + emit infoMessageChanged(); + } + } + + it = properties.find(QStringLiteral("percent")); + if (it != end) { + setPercent(it->toUInt()); + } + + it = properties.find(QStringLiteral("destUrl")); + if (it != end) { + const QUrl destUrl = QUrl(it->toUrl().adjusted(QUrl::StripTrailingSlash)); // urgh + updateField(destUrl, m_destUrl, &Job::destUrlChanged); + } + + it = properties.find(QStringLiteral("speed")); + if (it != end) { + setSpeed(it->value()); + } + + updateFieldFromProperties(properties, QStringLiteral("processedFiles"), m_processedFiles, &Job::processedFilesChanged); + updateFieldFromProperties(properties, QStringLiteral("processedBytes"), m_processedBytes, &Job::processedBytesChanged); + updateFieldFromProperties(properties, QStringLiteral("processedDirectories"), m_processedDirectories, &Job::processedDirectoriesChanged); + + updateFieldFromProperties(properties, QStringLiteral("totalFiles"), m_totalFiles, &Job::totalFilesChanged); + updateFieldFromProperties(properties, QStringLiteral("totalBytes"), m_totalBytes, &Job::totalBytesChanged); + updateFieldFromProperties(properties, QStringLiteral("totalDirectories"), m_totalDirectories, &Job::totalDirectoriesChanged); + + updateFieldFromProperties(properties, QStringLiteral("descriptionLabel1"), m_descriptionLabel1, &Job::descriptionLabel1Changed); + updateFieldFromProperties(properties, QStringLiteral("descriptionValue1"), m_descriptionValue1, &Job::descriptionValue1Changed); + updateFieldFromProperties(properties, QStringLiteral("descriptionLabel2"), m_descriptionLabel2, &Job::descriptionLabel2Changed); + updateFieldFromProperties(properties, QStringLiteral("descriptionValue2"), m_descriptionValue2, &Job::descriptionValue2Changed); + + it = properties.find(QStringLiteral("suspended")); + if (it != end) { + setSuspended(it->toBool()); + } + + updateHasDetails(); } diff --git a/libnotificationmanager/jobsmodel.cpp b/libnotificationmanager/jobsmodel.cpp --- a/libnotificationmanager/jobsmodel.cpp +++ b/libnotificationmanager/jobsmodel.cpp @@ -136,6 +136,12 @@ case Notifications::ConfigurableRole: return false; case Notifications::ExpiredRole: return job->expired(); case Notifications::DismissedRole: return job->dismissed(); + + // A job is usually either a long lasting operation you're aware about + // or a quick job you don't care about. + // When it's running, it's there, when it failed, it's persistent. + // There's hardly a reason why it should show up as "unread". + case Notifications::ReadRole: return true; } return QVariant(); diff --git a/libnotificationmanager/jobsmodel_p.cpp b/libnotificationmanager/jobsmodel_p.cpp --- a/libnotificationmanager/jobsmodel_p.cpp +++ b/libnotificationmanager/jobsmodel_p.cpp @@ -248,7 +248,7 @@ KService::Ptr service = KService::serviceByStorageId(applicationName); if (!service) { // HACK :) - service = KService::serviceByStorageId(QStringLiteral("org.kde.") + appName); + service = KService::serviceByStorageId(QLatin1String("org.kde.") + appName); } if (service) { @@ -324,6 +324,9 @@ connect(job, &Job::summaryChanged, this, [this, job] { scheduleUpdate(job, Notifications::SummaryRole); }); + connect(job, &Job::textChanged, this, [this, job] { + scheduleUpdate(job, Notifications::BodyRole); + }); connect(job, &Job::stateChanged, this, [this, job] { scheduleUpdate(job, Notifications::JobStateRole); // Timeout and Closable depend on state, signal a change for those, too @@ -349,26 +352,7 @@ scheduleUpdate(job, Notifications::DismissedRole); }); - // The following are used in generating the pretty job text - connect(job, &Job::processedFilesChanged, this, [this, job] { - scheduleUpdate(job, Notifications::BodyRole); - }); - connect(job, &Job::totalFilesChanged, this, [this, job] { - scheduleUpdate(job, Notifications::BodyRole); - }); - connect(job, &Job::descriptionValue1Changed, this, [this, job] { - scheduleUpdate(job, Notifications::BodyRole); - }); - connect(job, &Job::descriptionValue2Changed, this, [this, job] { - scheduleUpdate(job, Notifications::BodyRole); - }); - connect(job, &Job::destUrlChanged, this, [this, job] { - scheduleUpdate(job, Notifications::BodyRole); - emitJobUrlsChanged(); - }); - connect(job, &Job::errorTextChanged, this, [this, job] { - scheduleUpdate(job, Notifications::BodyRole); - }); + connect(job, &Job::destUrlChanged, this, &JobsModelPrivate::emitJobUrlsChanged); connect(job->d, &JobPrivate::closed, this, [this, job] { remove(job); @@ -502,6 +486,12 @@ job->setError(KIO::ERR_OWNER_DIED); job->setErrorText(i18n("Application closed unexpectedly.")); job->setState(Notifications::JobStateStopped); + + // basically JobPrivate::finish() + // update timestamp + job->resetUpdated(); + // when it was hidden in history, bring it up again + job->setDismissed(false); } Q_ASSERT(!m_serviceWatcher->watchedServices().contains(serviceName)); diff --git a/libnotificationmanager/kcfg/donotdisturbsettings.kcfg b/libnotificationmanager/kcfg/donotdisturbsettings.kcfg --- a/libnotificationmanager/kcfg/donotdisturbsettings.kcfg +++ b/libnotificationmanager/kcfg/donotdisturbsettings.kcfg @@ -10,6 +10,10 @@ + + true + + false diff --git a/libnotificationmanager/mirroredscreenstracker.cpp b/libnotificationmanager/mirroredscreenstracker.cpp new file mode 100644 --- /dev/null +++ b/libnotificationmanager/mirroredscreenstracker.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2019 Kai Uwe Broulik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mirroredscreenstracker_p.h" + +#include + +#include +#include +#include + +#include "debug.h" + +using namespace NotificationManager; + +MirroredScreensTracker::MirroredScreensTracker() + : QObject(nullptr) +{ + connect(new KScreen::GetConfigOperation(KScreen::GetConfigOperation::NoEDID), &KScreen::ConfigOperation::finished, this, + [this](KScreen::ConfigOperation *op) { + m_screenConfiguration = qobject_cast(op)->config(); + checkScreensMirrored(); + + KScreen::ConfigMonitor::instance()->addConfig(m_screenConfiguration); + connect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, + this, &MirroredScreensTracker::checkScreensMirrored); + }); +} + +MirroredScreensTracker::~MirroredScreensTracker() = default; + +MirroredScreensTracker::Ptr MirroredScreensTracker::createTracker() +{ + static QWeakPointer s_instance; + if (!s_instance) { + QSharedPointer ptr(new MirroredScreensTracker()); + s_instance = ptr.toWeakRef(); + return ptr; + } + return s_instance.toStrongRef(); +} + +bool MirroredScreensTracker::screensMirrored() const +{ + return m_screensMirrored; +} + +void MirroredScreensTracker::setScreensMirrored(bool mirrored) +{ + if (m_screensMirrored != mirrored) { + m_screensMirrored = mirrored; + emit screensMirroredChanged(mirrored); + } +} + +void MirroredScreensTracker::checkScreensMirrored() +{ + if (!m_screenConfiguration) { + setScreensMirrored(false); + return; + } + + const auto outputs = m_screenConfiguration->outputs(); + for (const KScreen::OutputPtr &output : outputs) { + if (!output->isConnected() || !output->isEnabled()) { + continue; + } + + for (const KScreen::OutputPtr &checkOutput : outputs) { + if (checkOutput == output || !checkOutput->isConnected() || !checkOutput->isEnabled()) { + continue; + } + + if (output->geometry().intersects(checkOutput->geometry())) { + qCDebug(NOTIFICATIONMANAGER) << "Screen geometry" << checkOutput->geometry() << "intersects" << output->geometry() << "- considering them to be mirrored"; + setScreensMirrored(true); + return; + } + } + } + + setScreensMirrored(false); +} diff --git a/libnotificationmanager/mirroredscreenstracker_p.h b/libnotificationmanager/mirroredscreenstracker_p.h new file mode 100644 --- /dev/null +++ b/libnotificationmanager/mirroredscreenstracker_p.h @@ -0,0 +1,67 @@ +/* + * Copyright 2019 Kai Uwe Broulik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include +#include + +#include + +namespace NotificationManager +{ + +/** + * @short Tracks whether there are any mirrored screens + * + * @author Kai Uwe Broulik + **/ +class MirroredScreensTracker : public QObject +{ + Q_OBJECT + +public: + ~MirroredScreensTracker(); + + using Ptr = QSharedPointer; + static Ptr createTracker(); + + bool screensMirrored() const; + /** + * Set whether screens are mirrored + * + * This is public so that automatic do not disturb mode when screens are mirrored + * can be disabled temporarily until screen configuration changes again. + */ + void setScreensMirrored(bool mirrored); + Q_SIGNAL void screensMirroredChanged(bool mirrored); + +private: + MirroredScreensTracker(); + Q_DISABLE_COPY(MirroredScreensTracker) + + void checkScreensMirrored(); + + KScreen::ConfigPtr m_screenConfiguration; + bool m_screensMirrored = false; + +}; + +} // namespace NotificationManager diff --git a/libnotificationmanager/notification.h b/libnotificationmanager/notification.h --- a/libnotificationmanager/notification.h +++ b/libnotificationmanager/notification.h @@ -58,6 +58,9 @@ QDateTime updated() const; void resetUpdated(); + bool read() const; + void setRead(bool read); + QString summary() const; void setSummary(const QString &summary); @@ -98,12 +101,20 @@ Notifications::Urgency urgency() const; void setUrgency(Notifications::Urgency urgency); + bool userActionFeedback() const; + int timeout() const; void setTimeout(int timeout); bool configurable() const; QString configureActionLabel() const; + bool hasReplyAction() const; + QString replyActionLabel() const; + QString replyPlaceholderText() const; + QString replySubmitButtonText() const; + QString replySubmitButtonIconName() const; + bool expired() const; void setExpired(bool expired); diff --git a/libnotificationmanager/notification.cpp b/libnotificationmanager/notification.cpp --- a/libnotificationmanager/notification.cpp +++ b/libnotificationmanager/notification.cpp @@ -35,14 +35,11 @@ #include #include -#include #include #include #include "debug.h" -#include "notifications.h" - using namespace NotificationManager; Notification::Private::Private() @@ -103,7 +100,7 @@ out.writeAttribute(QStringLiteral("alt"), alt); } - if (name == QLatin1String("a")) { + if (name == QLatin1Char('a')) { out.writeAttribute(QStringLiteral("href"), r.attributes().value("href").toString()); } } @@ -131,7 +128,7 @@ // The Text.StyledText format handles only html3.2 stuff and ' is html4 stuff // so we need to replace it here otherwise it will not render at all. - result = result.replace(QLatin1String("'"), QChar('\'')); + result.replace(QLatin1String("'"), QChar('\'')); return result; } @@ -383,8 +380,18 @@ } } + userActionFeedback = hints.value(QStringLiteral("x-kde-user-action-feedback")).toBool(); + if (userActionFeedback) { + // A confirmation of an explicit user interaction is assumed to have been seen by the user. + read = true; + } + urls = QUrl::fromStringList(hints.value(QStringLiteral("x-kde-urls")).toStringList()); + replyPlaceholderText = hints.value(QStringLiteral("x-kde-reply-placeholder-text")).toString(); + replySubmitButtonText = hints.value(QStringLiteral("x-kde-reply-submit-button-text")).toString(); + replySubmitButtonIconName = hints.value(QStringLiteral("x-kde-reply-submit-button-icon-name")).toString(); + // Underscored hints was in use in version 1.1 of the spec but has been // replaced by dashed hints in version 1.2. We need to support it for // users of the 1.2 version of the spec. @@ -487,6 +494,16 @@ d->updated = QDateTime::currentDateTimeUtc(); } +bool Notification::read() const +{ + return d->read; +} + +void Notification::setRead(bool read) +{ + d->read = read; +} + QString Notification::summary() const { return d->summary; @@ -602,6 +619,7 @@ d->hasDefaultAction = false; d->hasConfigureAction = false; + d->hasReplyAction = false; QStringList names; QStringList labels; @@ -622,6 +640,12 @@ continue; } + if (!d->hasReplyAction && name == QLatin1String("inline-reply")) { + d->hasReplyAction = true; + d->replyActionLabel = label; + continue; + } + names << name; labels << label; } @@ -645,6 +669,11 @@ return d->urgency; } +bool Notification::userActionFeedback() const +{ + return d->userActionFeedback; +} + int Notification::timeout() const { return d->timeout; @@ -665,6 +694,31 @@ return d->configureActionLabel; } +bool Notification::hasReplyAction() const +{ + return d->hasReplyAction; +} + +QString Notification::replyActionLabel() const +{ + return d->replyActionLabel; +} + +QString Notification::replyPlaceholderText() const +{ + return d->replyPlaceholderText; +} + +QString Notification::replySubmitButtonText() const +{ + return d->replySubmitButtonText; +} + +QString Notification::replySubmitButtonIconName() const +{ + return d->replySubmitButtonIconName; +} + bool Notification::expired() const { return d->expired; diff --git a/libnotificationmanager/notification_p.h b/libnotificationmanager/notification_p.h --- a/libnotificationmanager/notification_p.h +++ b/libnotificationmanager/notification_p.h @@ -60,6 +60,7 @@ uint id = 0; QDateTime created; QDateTime updated; + bool read = false; QString summary; QString body; @@ -87,8 +88,15 @@ QString notifyRcName; QString eventId; + bool hasReplyAction = false; + QString replyActionLabel; + QString replyPlaceholderText; + QString replySubmitButtonText; + QString replySubmitButtonIconName; + QList urls; + bool userActionFeedback = false; Notifications::Urgency urgency = Notifications::NormalUrgency; int timeout = -1; diff --git a/libnotificationmanager/notificationfilterproxymodel.cpp b/libnotificationmanager/notificationfilterproxymodel.cpp --- a/libnotificationmanager/notificationfilterproxymodel.cpp +++ b/libnotificationmanager/notificationfilterproxymodel.cpp @@ -137,21 +137,22 @@ return false; } - // If the application isn't configurable in any way, it doesn't deserve to be in the history - // since there's no way for the user to get rid of it there. - if (expired && !sourceIdx.data(Notifications::ConfigurableRole).toBool() - // jobs are never configurable so this only applies to notifications - && sourceIdx.data(Notifications::TypeRole).toInt() == Notifications::NotificationType) { + if (!m_showDismissed && sourceIdx.data(Notifications::DismissedRole).toBool()) { return false; } - if (!m_showDismissed && sourceIdx.data(Notifications::DismissedRole).toBool()) { - return false; + QString desktopEntry = sourceIdx.data(Notifications::DesktopEntryRole).toString(); + if (desktopEntry.isEmpty()) { + // For non-configurable notifications use the fake "@other" category. + if (!sourceIdx.data(Notifications::ConfigurableRole).toBool() + // jobs are never configurable so this only applies to notifications + && sourceIdx.data(Notifications::TypeRole).toInt() == Notifications::NotificationType) { + desktopEntry = QStringLiteral("@other"); + } } // Blacklist takes precedence over whitelist, i.e. when in doubt don't show if (!m_blacklistedDesktopEntries.isEmpty()) { - const QString desktopEntry = sourceIdx.data(Notifications::DesktopEntryRole).toString(); if (!desktopEntry.isEmpty() && m_blacklistedDesktopEntries.contains(desktopEntry)) { return false; } @@ -165,7 +166,6 @@ } if (!m_whitelistedDesktopEntries.isEmpty()) { - const QString desktopEntry = sourceIdx.data(Notifications::DesktopEntryRole).toString(); if (!desktopEntry.isEmpty() && m_whitelistedDesktopEntries.contains(desktopEntry)) { return true; } @@ -178,6 +178,11 @@ } } + const bool userActionFeedback = sourceIdx.data(Notifications::UserActionFeedbackRole).toBool(); + if (userActionFeedback) { + return true; + } + bool ok; const auto urgency = static_cast(sourceIdx.data(Notifications::UrgencyRole).toInt(&ok)); if (ok) { diff --git a/libnotificationmanager/notificationgroupcollapsingproxymodel.cpp b/libnotificationmanager/notificationgroupcollapsingproxymodel.cpp --- a/libnotificationmanager/notificationgroupcollapsingproxymodel.cpp +++ b/libnotificationmanager/notificationgroupcollapsingproxymodel.cpp @@ -199,13 +199,16 @@ if (m_expandUnread && m_lastRead.isValid()) { const QModelIndex sourceIdx = sourceModel()->index(source_row, 0, source_parent); - QDateTime time = sourceIdx.data(Notifications::UpdatedRole).toDateTime(); - if (!time.isValid()) { - time = sourceIdx.data(Notifications::CreatedRole).toDateTime(); - } - if (time.isValid() && m_lastRead < time) { - return true; + if (!sourceIdx.data(Notifications::ReadRole).toBool()) { + QDateTime time = sourceIdx.data(Notifications::UpdatedRole).toDateTime(); + if (!time.isValid()) { + time = sourceIdx.data(Notifications::CreatedRole).toDateTime(); + } + + if (time.isValid() && m_lastRead < time) { + return true; + } } } diff --git a/libnotificationmanager/notificationgroupcollapsingproxymodel_p.h b/libnotificationmanager/notificationgroupcollapsingproxymodel_p.h --- a/libnotificationmanager/notificationgroupcollapsingproxymodel_p.h +++ b/libnotificationmanager/notificationgroupcollapsingproxymodel_p.h @@ -62,7 +62,7 @@ void invalidateGroupRoles(); - int m_limit; + int m_limit = 0; QDateTime m_lastRead; bool m_expandUnread = false; diff --git a/libnotificationmanager/notificationgroupingproxymodel.cpp b/libnotificationmanager/notificationgroupingproxymodel.cpp --- a/libnotificationmanager/notificationgroupingproxymodel.cpp +++ b/libnotificationmanager/notificationgroupingproxymodel.cpp @@ -43,8 +43,12 @@ const QString aDesktopEntry = a.data(Notifications::DesktopEntryRole).toString(); const QString bDesktopEntry = b.data(Notifications::DesktopEntryRole).toString(); + const QString aOriginName = a.data(Notifications::OriginNameRole).toString(); + const QString bOriginName = a.data(Notifications::OriginNameRole).toString(); + return !aName.isEmpty() && aName == bName - && aDesktopEntry == bDesktopEntry; + && aDesktopEntry == bDesktopEntry + && aOriginName == bOriginName; } bool NotificationGroupingProxyModel::isGroup(int row) const @@ -471,23 +475,16 @@ return false; case Notifications::DesktopEntryRole: - for (int i = 0; i < rowCount(proxyIndex); ++i) { - const QString desktopEntry = index(i, 0, proxyIndex).data(Notifications::DesktopEntryRole).toString(); - if (!desktopEntry.isEmpty()) { - return desktopEntry; - } - } - return QString(); case Notifications::NotifyRcNameRole: + case Notifications::OriginNameRole: for (int i = 0; i < rowCount(proxyIndex); ++i) { - const QString notifyRcName = index(i, 0, proxyIndex).data(Notifications::NotifyRcNameRole).toString(); - if (!notifyRcName.isEmpty()) { - return notifyRcName; + const QString stringData = index(i, 0, proxyIndex).data(role).toString(); + if (!stringData.isEmpty()) { + return stringData; } } return QString(); - case Notifications::ConfigurableRole: // if there is any configurable child item for (int i = 0; i < rowCount(proxyIndex); ++i) { if (index(i, 0, proxyIndex).data(Notifications::ConfigurableRole).toBool()) { diff --git a/libnotificationmanager/notifications.h b/libnotificationmanager/notifications.h --- a/libnotificationmanager/notifications.h +++ b/libnotificationmanager/notifications.h @@ -256,7 +256,16 @@ ClosableRole, ///< Whether the item can be closed. Notifications are always closable, jobs are only when in JobStateStopped. ExpiredRole, ///< The notification timed out and closed. Actions on it cannot be invoked anymore. - DismissedRole ///< The notification got temporarily hidden by the user but could still be interacted with. + DismissedRole, ///< The notification got temporarily hidden by the user but could still be interacted with. + ReadRole, ///< Whether the notification got read by the user. If true, the notification isn't considered unread even if created after lastRead. @since 5.17 + + UserActionFeedbackRole, ///< Whether this notification is a response/confirmation to an explicit user action. @since 5.18 + + HasReplyActionRole, ///< Whether the action has a reply action. @since 5.18 + ReplyActionLabelRole, ///< The user-visible label for the reply action. @since 5.18 + ReplyPlaceholderTextRole, ///< A custom placeholder text for the reply action, e.g. "Reply to Max...". @since 5.18 + ReplySubmitButtonTextRole, ///< A custom text for the reply submit button, e.g. "Submit Comment". @since 5.18 + ReplySubmitButtonIconNameRole, ///< A custom icon name for the reply submit button. @since 5.18 }; Q_ENUM(Roles) @@ -429,6 +438,14 @@ */ Q_INVOKABLE void invokeAction(const QModelIndex &idx, const QString &actionId); + /** + * @brief Reply to a notification + * + * Replies to the given notification with the given text. + * @since 5.18 + */ + Q_INVOKABLE void reply(const QModelIndex &idx, const QString &text); + /** * @brief Start automatic timeout of notifications * diff --git a/libnotificationmanager/notifications.cpp b/libnotificationmanager/notifications.cpp --- a/libnotificationmanager/notifications.cpp +++ b/libnotificationmanager/notifications.cpp @@ -269,14 +269,14 @@ ++active; } - QDateTime date = idx.data(Notifications::UpdatedRole).toDateTime(); - if (!date.isValid()) { - date = idx.data(Notifications::CreatedRole).toDateTime(); - } + const bool read = idx.data(Notifications::ReadRole).toBool(); + if (!active && !read) { + QDateTime date = idx.data(Notifications::UpdatedRole).toDateTime(); + if (!date.isValid()) { + date = idx.data(Notifications::CreatedRole).toDateTime(); + } - // TODO Jobs could also be unread? - if (notificationsModel) { - if (!active && date > notificationsModel->lastRead()) { + if (notificationsModel && date > notificationsModel->lastRead()) { ++unread; } } @@ -726,6 +726,13 @@ } } +void Notifications::reply(const QModelIndex &idx, const QString &text) +{ + if (d->notificationsModel) { + d->notificationsModel->reply(Private::notificationId(idx), text); + } +} + void Notifications::startTimeout(const QModelIndex &idx) { startTimeout(Private::notificationId(idx)); diff --git a/libnotificationmanager/notificationsmodel.h b/libnotificationmanager/notificationsmodel.h --- a/libnotificationmanager/notificationsmodel.h +++ b/libnotificationmanager/notificationsmodel.h @@ -43,14 +43,16 @@ void setLastRead(const QDateTime &lastRead); QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; void expire(uint notificationId); void close(uint notificationId); void configure(uint notificationId); void configure(const QString &desktopEntry, const QString ¬ifyRcName, const QString &eventId); void invokeDefaultAction(uint notificationId); void invokeAction(uint notificationId, const QString &actionName); + void reply(uint notificationId, const QString &text); void startTimeout(uint notificationId); void stopTimeout(uint notificationId); diff --git a/libnotificationmanager/notificationsmodel.cpp b/libnotificationmanager/notificationsmodel.cpp --- a/libnotificationmanager/notificationsmodel.cpp +++ b/libnotificationmanager/notificationsmodel.cpp @@ -300,19 +300,47 @@ case Notifications::UrlsRole: return QVariant::fromValue(notification.urls()); case Notifications::UrgencyRole: return static_cast(notification.urgency()); + case Notifications::UserActionFeedbackRole: return notification.userActionFeedback(); case Notifications::TimeoutRole: return notification.timeout(); case Notifications::ClosableRole: return true; case Notifications::ConfigurableRole: return notification.configurable(); case Notifications::ConfigureActionLabelRole: return notification.configureActionLabel(); case Notifications::ExpiredRole: return notification.expired(); + case Notifications::ReadRole: return notification.read(); + + case Notifications::HasReplyActionRole: return notification.hasReplyAction(); + case Notifications::ReplyActionLabelRole: return notification.replyActionLabel(); + case Notifications::ReplyPlaceholderTextRole: return notification.replyPlaceholderText(); + case Notifications::ReplySubmitButtonTextRole: return notification.replySubmitButtonText(); + case Notifications::ReplySubmitButtonIconNameRole: return notification.replySubmitButtonIconName(); } return QVariant(); } +bool NotificationsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!checkIndex(index)) { + return false; + } + + Notification ¬ification = d->notifications[index.row()]; + + switch (role) { + case Notifications::ReadRole: + if (value.toBool() != notification.read()) { + notification.setRead(value.toBool()); + return true; + } + break; + } + + return false; +} + int NotificationsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { @@ -417,6 +445,22 @@ Server::self().invokeAction(notificationId, actionName); } +void NotificationsModel::reply(uint notificationId, const QString &text) +{ + const int row = d->rowOfNotification(notificationId); + if (row == -1) { + return; + } + + const Notification ¬ification = d->notifications.at(row); + if (!notification.hasReplyAction()) { + qCWarning(NOTIFICATIONMANAGER) << "Trying to reply to a notification which doesn't have a reply action"; + return; + } + + Server::self().reply(notificationId, text); +} + void NotificationsModel::startTimeout(uint notificationId) { const int row = d->rowOfNotification(notificationId); diff --git a/libnotificationmanager/plasmanotifyrc b/libnotificationmanager/plasmanotifyrc --- a/libnotificationmanager/plasmanotifyrc +++ b/libnotificationmanager/plasmanotifyrc @@ -9,3 +9,12 @@ # Klipper notifications might contain sensitive information [Applications][org.kde.klipper] ShowInHistory=false + +# DrKonqi creates a tray icon, no point in being in history +[Applications][org.kde.drkonqi] +ShowInHistory=false + +# Non-configurable applications don't deserve to be in history by default +[Applications][@other] +ShowInHistory=false +ShowBadges=false # wouldn't work anyway diff --git a/libnotificationmanager/server.h b/libnotificationmanager/server.h --- a/libnotificationmanager/server.h +++ b/libnotificationmanager/server.h @@ -29,6 +29,7 @@ class Notification; +class ServerInfo; class ServerPrivate; /** @@ -40,6 +41,34 @@ { Q_OBJECT + /** + * Whether the notification service could be registered. + * Call @c init() to register. + */ + Q_PROPERTY(bool valid READ isValid NOTIFY validChanged) + + /** + * Information about the current owner of the Notification service. + * + * This can be used to tell the user which application is currently + * owning the service in case service registration failed. + * + * This is never null, even if there is no notification service running. + * + * @since 5.18 + */ + Q_PROPERTY(NotificationManager::ServerInfo *currentOwner READ currentOwner CONSTANT) + + /** + * Whether notifications are currently inhibited. + * + * This is what is announced to other applicatons on the bus. + * + * @note This does not keep track of inhibitions on its own, + * you need to calculate this yourself and update the property accordingly. + */ + Q_PROPERTY(bool inhibited READ inhibited WRITE setInhibited NOTIFY inhibitedChanged) + public: ~Server() override; @@ -68,10 +97,31 @@ bool isValid() const; /** - * Whether an application requested to inhibit notifications. + * Information about the current owner of the Notification service. + * @since 5.18 + */ + ServerInfo *currentOwner() const; + + /** + * Whether notifications are currently inhibited. + * @since 5.17 */ bool inhibited() const; + /** + * Whether notifications are currently effectively inhibited. + * + * @note You need to keep track of inhibitions and call this + * yourself when appropriate. + * @since 5.17 + */ + void setInhibited(bool inhibited); + + /** + * Whether an application requested to inhibit notifications. + */ + bool inhibitedByApplication() const; + // should we return a struct or pair or something? QStringList inhibitionApplications() const; QStringList inhibitionReasons() const; @@ -98,6 +148,15 @@ */ void invokeAction(uint id, const QString &actionName); + /** + * Sends a notification reply text + * + * @param id The notification ID + * @param text The reply message text + * @since 5.18 + */ + void reply(uint id, const QString &text); + /** * Adds a notification * @@ -109,6 +168,14 @@ uint add(const Notification ¬ification); Q_SIGNALS: + /** + * Emitted when the notification service validity changes, + * because it sucessfully registered the service or lost + * ownership of it. + * @since 5.18 + */ + void validChanged(); + /** * Emitted when a notification was added. * This is emitted regardless of any filtering rules or user settings. @@ -130,12 +197,18 @@ void notificationRemoved(uint id, CloseReason reason); /** - * Emitted when inhibitions have been changed. Becomes true - * as soon as there is one inhibition and becomes false again - * when all inhibitions have been lifted. + * Emitted when the inhibited state changed. */ void inhibitedChanged(bool inhibited); + /** + * Emitted when inhibitions by application have been changed. + * Becomes true as soon as there is one inhibition and becomes + * false again when all inhibitions have been lifted. + * @since 5.17 + */ + void inhibitedByApplicationChanged(bool inhibited); + /** * Emitted when the list of applications holding a notification * inhibition changes. diff --git a/libnotificationmanager/server.cpp b/libnotificationmanager/server.cpp --- a/libnotificationmanager/server.cpp +++ b/libnotificationmanager/server.cpp @@ -34,11 +34,14 @@ : QObject(parent) , d(new ServerPrivate(this)) { + connect(d.data(), &ServerPrivate::validChanged, this, &Server::validChanged); connect(d.data(), &ServerPrivate::inhibitedChanged, this, [this] { emit inhibitedChanged(inhibited()); }); - connect(d.data(), &ServerPrivate::inhibitionAdded, this, &Server::inhibitionApplicationsChanged); - connect(d.data(), &ServerPrivate::inhibitionRemoved, this, &Server::inhibitionApplicationsChanged); + connect(d.data(), &ServerPrivate::externalInhibitedChanged, this, [this] { + emit inhibitedByApplicationChanged(inhibitedByApplication()); + }); + connect(d.data(), &ServerPrivate::externalInhibitionsChanged, this, &Server::inhibitionApplicationsChanged); connect(d.data(), &ServerPrivate::serviceOwnershipLost, this, &Server::serviceOwnershipLost); } @@ -60,6 +63,11 @@ return d->m_valid; } +ServerInfo *Server::currentOwner() const +{ + return d->currentOwner(); +} + void Server::closeNotification(uint notificationId, CloseReason reason) { emit notificationRemoved(notificationId, reason); @@ -72,6 +80,11 @@ emit d->ActionInvoked(notificationId, actionName); } +void Server::reply(uint notificationId, const QString &text) +{ + emit d->NotificationReplied(notificationId, text); +} + uint Server::add(const Notification ¬ification) { return d->add(notification); @@ -82,10 +95,20 @@ return d->inhibited(); } +void Server::setInhibited(bool inhibited) +{ + d->setInhibited(inhibited); +} + +bool Server::inhibitedByApplication() const +{ + return d->externalInhibited(); +} + QStringList Server::inhibitionApplications() const { QStringList applications; - const auto inhibitions = d->inhibitions(); + const auto inhibitions = d->externalInhibitions(); applications.reserve(inhibitions.count()); for (const auto &inhibition : inhibitions) { applications.append(!inhibition.applicationName.isEmpty() ? inhibition.applicationName : inhibition.desktopEntry); @@ -96,7 +119,7 @@ QStringList Server::inhibitionReasons() const { QStringList reasons; - const auto inhibitions = d->inhibitions(); + const auto inhibitions = d->externalInhibitions(); reasons.reserve(inhibitions.count()); for (const auto &inhibition : inhibitions) { reasons.append(inhibition.reason); @@ -106,5 +129,5 @@ void Server::clearInhibitions() { - d->clearInhibitions(); + d->clearExternalInhibitions(); } diff --git a/libnotificationmanager/server_p.h b/libnotificationmanager/server_p.h --- a/libnotificationmanager/server_p.h +++ b/libnotificationmanager/server_p.h @@ -39,6 +39,8 @@ namespace NotificationManager { +class ServerInfo; + class Q_DECL_HIDDEN ServerPrivate : public QObject, protected QDBusContext { Q_OBJECT @@ -70,33 +72,57 @@ // DBus void NotificationClosed(uint id, uint reason); void ActionInvoked(uint id, const QString &actionKey); + // non-standard + void NotificationReplied(uint id, const QString &text); + + void validChanged(); void inhibitedChanged(); - void inhibitionAdded(); - void inhibitionRemoved(); + + void externalInhibitedChanged(); + void externalInhibitionsChanged(); + void serviceOwnershipLost(); public: // stuff used by public class + friend class ServerInfo; + static QString notificationServiceName(); + bool init(); uint add(const Notification ¬ification); - QList inhibitions() const; - void clearInhibitions(); + ServerInfo *currentOwner() const; + + // Server only handles external application inhibitions but we still want the Inhibited property + // expose the actual inhibition state for applications to check. + void setInhibited(bool inhibited); + + bool externalInhibited() const; + QList externalInhibitions() const; + void clearExternalInhibitions(); bool m_valid = false; uint m_highestNotificationId = 1; private slots: void onBroadcastNotification(const QMap &properties); private: - void onServiceUnregistered(const QString &serviceName); + void onServiceOwnershipLost(const QString &serviceName); + void onInhibitionServiceUnregistered(const QString &serviceName); + void onInhibitedChanged(); // emit DBus change signal + + bool m_dbusObjectValid = false; + + mutable QScopedPointer m_currentOwner; QDBusServiceWatcher *m_inhibitionWatcher = nullptr; uint m_highestInhibitionCookie = 0; - QHash m_inhibitions; + QHash m_externalInhibitions; QHash m_inhibitionServices; + bool m_inhibited = false; + Notification m_lastNotification; }; diff --git a/libnotificationmanager/server_p.cpp b/libnotificationmanager/server_p.cpp --- a/libnotificationmanager/server_p.cpp +++ b/libnotificationmanager/server_p.cpp @@ -28,6 +28,7 @@ #include "notification_p.h" #include "server.h" +#include "serverinfo.h" #include "utils_p.h" @@ -47,89 +48,88 @@ { m_inhibitionWatcher->setConnection(QDBusConnection::sessionBus()); m_inhibitionWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); - connect(m_inhibitionWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &ServerPrivate::onServiceUnregistered); + connect(m_inhibitionWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &ServerPrivate::onInhibitionServiceUnregistered); } ServerPrivate::~ServerPrivate() = default; +QString ServerPrivate::notificationServiceName() +{ + return QStringLiteral("org.freedesktop.Notifications"); +} + +ServerInfo *ServerPrivate::currentOwner() const +{ + if (!m_currentOwner) { + m_currentOwner.reset(new ServerInfo()); + } + + return m_currentOwner.data(); +} + bool ServerPrivate::init() { if (m_valid) { return true; } new NotificationsAdaptor(this); - if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/freedesktop/Notifications"), this)) { + if (!m_dbusObjectValid) { // if already registered, don't fail here + m_dbusObjectValid = QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/freedesktop/Notifications"), this); + } + + if (!m_dbusObjectValid) { qCWarning(NOTIFICATIONMANAGER) << "Failed to register Notification DBus object"; return false; } // Only the "dbus master" (effectively plasmashell) should be the true owner of notifications const bool master = Utils::isDBusMaster(); - const QString notificationService = QStringLiteral("org.freedesktop.Notifications"); - QDBusConnectionInterface *dbusIface = QDBusConnection::sessionBus().interface(); if (!master) { - connect(dbusIface, &QDBusConnectionInterface::serviceUnregistered, this, [=](const QString &serviceName) { - if (serviceName == notificationService) { - qCDebug(NOTIFICATIONMANAGER) << "Lost ownership of" << serviceName << "service"; - emit serviceOwnershipLost(); - } - }); + // NOTE this connects to whether the application lost ownership of given service + // This is not a wildcard listener for all unregistered services on the bus! + connect(dbusIface, &QDBusConnectionInterface::serviceUnregistered, this, &ServerPrivate::onServiceOwnershipLost, Qt::UniqueConnection); } - auto registration = dbusIface->registerService(notificationService, + auto registration = dbusIface->registerService(notificationServiceName(), master ? QDBusConnectionInterface::ReplaceExistingService : QDBusConnectionInterface::DontQueueService, master ? QDBusConnectionInterface::DontAllowReplacement : QDBusConnectionInterface::AllowReplacement ); if (registration.value() != QDBusConnectionInterface::ServiceRegistered) { qCWarning(NOTIFICATIONMANAGER) << "Failed to register Notification service on DBus"; return false; } - connect(this, &ServerPrivate::inhibitedChanged, this, [this] { - // emit DBus change signal... - QDBusMessage signal = QDBusMessage::createSignal( - QStringLiteral("/org/freedesktop/Notifications"), - QStringLiteral("org.freedesktop.DBus.Properties"), - QStringLiteral("PropertiesChanged") - ); - - signal.setArguments({ - QStringLiteral("org.freedesktop.Notifications"), - QVariantMap{ // updated - {QStringLiteral("Inhibited"), inhibited()}, - }, - QStringList() // invalidated - }); - - QDBusConnection::sessionBus().send(signal); - }); + connect(this, &ServerPrivate::inhibitedChanged, this, &ServerPrivate::onInhibitedChanged, Qt::UniqueConnection); qCDebug(NOTIFICATIONMANAGER) << "Registered Notification service on DBus"; KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Notifications")); const bool broadcastsEnabled = config.readEntry("ListenForBroadcasts", false); if (broadcastsEnabled) { qCDebug(NOTIFICATIONMANAGER) << "Notification server is configured to listen for broadcasts"; + // NOTE Keep disconnect() call in onServiceOwnershipLost in sync if you change this! QDBusConnection::systemBus().connect({}, {}, QStringLiteral("org.kde.BroadcastNotifications"), QStringLiteral("Notify"), this, SLOT(onBroadcastNotification(QMap))); } m_valid = true; + emit validChanged(); + return true; } uint ServerPrivate::Notify(const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout) { const bool wasReplaced = replaces_id > 0; - int notificationId = 0; + uint notificationId = 0; if (wasReplaced) { notificationId = replaces_id; } else { @@ -231,6 +231,7 @@ QStringLiteral("body-images"), QStringLiteral("icon-static"), QStringLiteral("actions"), + QStringLiteral("inline-reply"), QStringLiteral("x-kde-urls"), QStringLiteral("x-kde-origin-name"), @@ -323,33 +324,80 @@ ++m_highestInhibitionCookie; - m_inhibitions.insert(m_highestInhibitionCookie, { + const bool oldExternalInhibited = externalInhibited(); + + m_externalInhibitions.insert(m_highestInhibitionCookie, { desktop_entry, applicationName, reason, hints }); m_inhibitionServices.insert(m_highestInhibitionCookie, dbusService); - emit inhibitedChanged(); - emit inhibitionAdded(); + if (externalInhibited() != oldExternalInhibited) { + emit externalInhibitedChanged(); + } + emit externalInhibitionsChanged(); return m_highestInhibitionCookie; } -void ServerPrivate::onServiceUnregistered(const QString &serviceName) +void ServerPrivate::onServiceOwnershipLost(const QString &serviceName) +{ + if (serviceName != notificationServiceName()) { + return; + } + + qCDebug(NOTIFICATIONMANAGER) << "Lost ownership of" << serviceName << "service"; + + disconnect(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::serviceUnregistered, + this, &ServerPrivate::onServiceOwnershipLost); + disconnect(this, &ServerPrivate::inhibitedChanged, this, &ServerPrivate::onInhibitedChanged); + + QDBusConnection::systemBus().disconnect({}, {}, QStringLiteral("org.kde.BroadcastNotifications"), + QStringLiteral("Notify"), this, SLOT(onBroadcastNotification(QMap))); + + m_valid = false; + + emit validChanged(); + emit serviceOwnershipLost(); +} + +void ServerPrivate::onInhibitionServiceUnregistered(const QString &serviceName) { qCDebug(NOTIFICATIONMANAGER) << "Inhibition service unregistered" << serviceName; - const uint cookie = m_inhibitionServices.key(serviceName); - if (!cookie) { + const QList cookies = m_inhibitionServices.keys(serviceName); + if (cookies.isEmpty()) { qCInfo(NOTIFICATIONMANAGER) << "Unknown inhibition service unregistered" << serviceName; return; } // We do lookups in there again... - UnInhibit(cookie); + for (uint cookie : cookies) { + UnInhibit(cookie); + } +} + +void ServerPrivate::onInhibitedChanged() +{ + // emit DBus change signal... + QDBusMessage signal = QDBusMessage::createSignal( + QStringLiteral("/org/freedesktop/Notifications"), + QStringLiteral("org.freedesktop.DBus.Properties"), + QStringLiteral("PropertiesChanged") + ); + + signal.setArguments({ + QStringLiteral("org.freedesktop.Notifications"), + QVariantMap{ // updated + {QStringLiteral("Inhibited"), inhibited()}, + }, + QStringList() // invalidated + }); + + QDBusConnection::sessionBus().send(signal); } void ServerPrivate::UnInhibit(uint cookie) @@ -364,37 +412,48 @@ } m_inhibitionWatcher->removeWatchedService(service); - m_inhibitions.remove(cookie); + m_externalInhibitions.remove(cookie); m_inhibitionServices.remove(cookie); - if (m_inhibitions.isEmpty()) { - emit inhibitedChanged(); - emit inhibitionRemoved(); + if (m_externalInhibitions.isEmpty()) { + emit externalInhibitedChanged(); } + emit externalInhibitionsChanged(); } -QList ServerPrivate::inhibitions() const +QList ServerPrivate::externalInhibitions() const { - return m_inhibitions.values(); + return m_externalInhibitions.values(); } bool ServerPrivate::inhibited() const { - // TODO this currently only returns whether an app has an inhibition going, - // there's no way for apps to query whether user enabled do not disturb from the applet - // so they could change their behavior. - return !m_inhibitions.isEmpty(); + return m_inhibited; } -void ServerPrivate::clearInhibitions() +void ServerPrivate::setInhibited(bool inhibited) { - if (m_inhibitions.isEmpty()) { + if (m_inhibited != inhibited) { + m_inhibited = inhibited; + emit inhibitedChanged(); + } +} + +bool ServerPrivate::externalInhibited() const +{ + return !m_externalInhibitions.isEmpty(); +} + +void ServerPrivate::clearExternalInhibitions() +{ + if (m_externalInhibitions.isEmpty()) { return; } m_inhibitionWatcher->setWatchedServices(QStringList()); // remove all watches m_inhibitionServices.clear(); - m_inhibitions.clear(); - emit inhibitedChanged(); - emit inhibitionRemoved(); + m_externalInhibitions.clear(); + + emit externalInhibitedChanged(); + emit externalInhibitionsChanged(); } diff --git a/libnotificationmanager/serverinfo.h b/libnotificationmanager/serverinfo.h new file mode 100644 --- /dev/null +++ b/libnotificationmanager/serverinfo.h @@ -0,0 +1,83 @@ +/* + * Copyright 2019 Kai Uwe Broulik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include "notificationmanager_export.h" + +#include +#include +#include + +namespace NotificationManager +{ + +/** + * @short Information about the notification server + * + * Provides information such as vendor, name, version of the notification server. + * + * @author Kai Uwe Broulik + **/ +class NOTIFICATIONMANAGER_EXPORT ServerInfo : public QObject +{ + Q_OBJECT + + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + + Q_PROPERTY(QString vendor READ vendor NOTIFY vendorChanged) + + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + + Q_PROPERTY(QString version READ version NOTIFY versionChanged) + + Q_PROPERTY(QString specVersion READ specVersion NOTIFY specVersionChanged) + +public: + explicit ServerInfo(QObject *parent = nullptr); + ~ServerInfo() override; + + enum class Status { + Unknown = -1, + NotRunning, + Running + }; + Q_ENUM(Status) + + Status status() const; + QString vendor() const; + QString name() const; + QString version() const; + QString specVersion() const; + +Q_SIGNALS: + void statusChanged(Status status); + void vendorChanged(const QString &vendor); + void nameChanged(const QString &name); + void versionChanged(const QString &version); + void specVersionChanged(const QString &specVersion); + +private: + class Private; + QScopedPointer d; + +}; + +} // namespace NotificationManager diff --git a/libnotificationmanager/serverinfo.cpp b/libnotificationmanager/serverinfo.cpp new file mode 100644 --- /dev/null +++ b/libnotificationmanager/serverinfo.cpp @@ -0,0 +1,176 @@ +/* + * Copyright 2018 Kai Uwe Broulik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "serverinfo.h" + +#include "server_p.h" // for notificationServiceName + +#include "debug.h" + +#include +#include +#include +#include +#include + +using namespace NotificationManager; + +class Q_DECL_HIDDEN ServerInfo::Private +{ +public: + Private(ServerInfo *q); + ~Private(); + + void setStatus(ServerInfo::Status status); + void setServerInformation(const QString &vendor, + const QString &name, + const QString &version, + const QString &specVersion); + + void updateServerInformation(); + + ServerInfo *q; + + ServerInfo::Status status = ServerInfo::Status::Unknown; + + QString vendor; + QString name; + QString version; + QString specVersion; +}; + +ServerInfo::Private::Private(ServerInfo *q) + : q(q) +{ + +} + +ServerInfo::Private::~Private() = default; + +void ServerInfo::Private::setStatus(ServerInfo::Status status) +{ + if (this->status != status) { + this->status = status; + emit q->statusChanged(status); + } +} + +void ServerInfo::Private::setServerInformation(const QString &vendor, + const QString &name, + const QString &version, + const QString &specVersion) +{ + if (this->vendor != vendor) { + this->vendor = vendor; + emit q->vendorChanged(vendor); + } + if (this->name != name) { + this->name = name; + emit q->nameChanged(name); + } + if (this->version != version) { + this->version = version; + emit q->versionChanged(version); + } + if (this->specVersion != specVersion) { + this->specVersion = specVersion; + emit q->specVersionChanged(specVersion); + } +} + +void ServerInfo::Private::updateServerInformation() +{ + // Check whether the service is running to avoid DBus-activating plasma_waitforname and getting stuck there. + if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerPrivate::notificationServiceName())) { + setStatus(ServerInfo::Status::NotRunning); + setServerInformation({}, {}, {}, {}); + return; + } + + QDBusMessage msg = QDBusMessage::createMethodCall(ServerPrivate::notificationServiceName(), + QStringLiteral("/org/freedesktop/Notifications"), + QStringLiteral("org.freedesktop.Notifications"), + QStringLiteral("GetServerInformation")); + auto call = QDBusConnection::sessionBus().asyncCall(msg); + + auto *watcher = new QDBusPendingCallWatcher(call, q); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, [this](QDBusPendingCallWatcher *watcher) { + QDBusPendingReply reply = *watcher; + watcher->deleteLater(); + + if (reply.isError()) { + qCWarning(NOTIFICATIONMANAGER) << "Failed to determine notification server information" << reply.error().message(); + // Should this still be "Running" as technically it is? + // But if it is not even responding to this properly, who knows what it'll to with an actual notification + setStatus(Status::Unknown); + setServerInformation({}, {}, {}, {}); + return; + } + + const QString name = reply.argumentAt(0).toString(); + const QString vendor = reply.argumentAt(1).toString(); + const QString version = reply.argumentAt(2).toString(); + const QString specVersion = reply.argumentAt(3).toString(); + + setServerInformation(vendor, name, version, specVersion); + setStatus(ServerInfo::Status::Running); + }); +} + +ServerInfo::ServerInfo(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ + auto *watcher = new QDBusServiceWatcher(ServerPrivate::notificationServiceName(), + QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForOwnerChange, this); + connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, [this]() { + d->updateServerInformation(); + }); + + d->updateServerInformation(); +} + +ServerInfo::~ServerInfo() = default; + +ServerInfo::Status ServerInfo::status() const +{ + return d->status; +} + +QString ServerInfo::vendor() const +{ + return d->vendor; +} + +QString ServerInfo::name() const +{ + return d->name; +} + +QString ServerInfo::version() const +{ + return d->version; +} + +QString ServerInfo::specVersion() const +{ + return d->specVersion; +} diff --git a/libnotificationmanager/settings.h b/libnotificationmanager/settings.h --- a/libnotificationmanager/settings.h +++ b/libnotificationmanager/settings.h @@ -169,6 +169,28 @@ READ notificationInhibitionReasons NOTIFY notificationInhibitionApplicationsChanged) + /** + * Whether to enable do not disturb mode when screens are mirrored/overlapping + * + * @since 5.17 + */ + Q_PROPERTY(bool inhibitNotificationsWhenScreensMirrored + READ inhibitNotificationsWhenScreensMirrored + WRITE setInhibitNotificationsWhenScreensMirrored + NOTIFY settingsChanged) + + /** + * Whether there currently are mirrored/overlapping screens + * + * This property is only updated when @c inhibitNotificationsWhenScreensMirrored + * is set to true, otherwise it is always false. + * You can assign false to this property if you want to temporarily revoke automatic do not disturb + * mode when screens are mirrored until the screen configuration changes. + * + * @since 5.17 + */ + Q_PROPERTY(bool screensMirrored READ screensMirrored WRITE setScreensMirrored NOTIFY screensMirroredChanged) + /** * Whether notification sounds should be disabled * @@ -299,6 +321,12 @@ QStringList notificationInhibitionApplications() const; QStringList notificationInhibitionReasons() const; + bool inhibitNotificationsWhenScreensMirrored() const; + void setInhibitNotificationsWhenScreensMirrored(bool mirrored); + + bool screensMirrored() const; + void setScreensMirrored(bool enable); + bool notificationSoundsInhibited() const; void setNotificationSoundsInhibited(bool inhibited); @@ -321,6 +349,8 @@ void notificationsInhibitedByApplicationChanged(bool notificationsInhibitedByApplication); void notificationInhibitionApplicationsChanged(); + void screensMirroredChanged(); + private: class Private; QScopedPointer d; diff --git a/libnotificationmanager/settings.cpp b/libnotificationmanager/settings.cpp --- a/libnotificationmanager/settings.cpp +++ b/libnotificationmanager/settings.cpp @@ -26,6 +26,7 @@ #include #include "server.h" +#include "mirroredscreenstracker_p.h" #include "debug.h" // Settings @@ -59,6 +60,8 @@ KConfigWatcher::Ptr watcher; QMetaObject::Connection watcherConnection; + MirroredScreensTracker::Ptr mirroredScreensTracker; + bool live = false; // set to true initially in constructor bool dirty = false; @@ -176,10 +179,15 @@ setLive(true); - connect(&Server::self(), &Server::inhibitedChanged, + connect(&Server::self(), &Server::inhibitedByApplicationChanged, this, &Settings::notificationsInhibitedByApplicationChanged); connect(&Server::self(), &Server::inhibitionApplicationsChanged, this, &Settings::notificationInhibitionApplicationsChanged); + + if (DoNotDisturbSettings::whenScreensMirrored()) { + d->mirroredScreensTracker = MirroredScreensTracker::createTracker(); + connect(d->mirroredScreensTracker.data(), &MirroredScreensTracker::screensMirroredChanged, this, &Settings::screensMirroredChanged); + } } Settings::~Settings() @@ -300,6 +308,22 @@ if (group.name() == QLatin1String("DoNotDisturb")) { DoNotDisturbSettings::self()->load(); + + bool emitScreensMirroredChanged = false; + if (DoNotDisturbSettings::whenScreensMirrored()) { + if (!d->mirroredScreensTracker) { + d->mirroredScreensTracker = MirroredScreensTracker::createTracker(); + emitScreensMirroredChanged = d->mirroredScreensTracker->screensMirrored(); + connect(d->mirroredScreensTracker.data(), &MirroredScreensTracker::screensMirroredChanged, this, &Settings::screensMirroredChanged); + } + } else if (d->mirroredScreensTracker) { + emitScreensMirroredChanged = d->mirroredScreensTracker->screensMirrored(); + d->mirroredScreensTracker.reset(); + } + + if (emitScreensMirroredChanged) { + emit screensMirroredChanged(); + } } else if (group.name() == QLatin1String("Notifications")) { NotificationSettings::self()->load(); } else if (group.name() == QLatin1String("Jobs")) { @@ -528,7 +552,7 @@ bool Settings::notificationsInhibitedByApplication() const { - return Server::self().inhibited(); + return Server::self().inhibitedByApplication(); } QStringList Settings::notificationInhibitionApplications() const @@ -541,6 +565,38 @@ return Server::self().inhibitionReasons(); } +bool Settings::inhibitNotificationsWhenScreensMirrored() const +{ + return DoNotDisturbSettings::whenScreensMirrored(); +} + +void Settings::setInhibitNotificationsWhenScreensMirrored(bool inhibit) +{ + if (inhibit == inhibitNotificationsWhenScreensMirrored()) { + return; + } + + DoNotDisturbSettings::setWhenScreensMirrored(inhibit); + d->setDirty(true); +} + +bool Settings::screensMirrored() const +{ + return d->mirroredScreensTracker && d->mirroredScreensTracker->screensMirrored(); +} + +void Settings::setScreensMirrored(bool mirrored) +{ + if (mirrored) { + qCWarning(NOTIFICATIONMANAGER) << "Cannot forcefully set screens mirrored"; + return; + } + + if (d->mirroredScreensTracker) { + d->mirroredScreensTracker->setScreensMirrored(mirrored); + } +} + void Settings::revokeApplicationInhibitions() { Server::self().clearInhibitions(); diff --git a/libnotificationmanager/utils.cpp b/libnotificationmanager/utils.cpp --- a/libnotificationmanager/utils.cpp +++ b/libnotificationmanager/utils.cpp @@ -30,23 +30,19 @@ #include -#include -#include +#include using namespace NotificationManager; QString Utils::processNameFromPid(uint pid) { - KSysGuard::Processes procs; - procs.updateOrAddProcess(pid); + auto processInfo = KProcessList::processInfo(pid); - KSysGuard::Process *proc = procs.getProcess(pid); - - if (!proc) { + if (!processInfo.isValid()) { return QString(); } - return proc->name(); + return processInfo.name(); } QString Utils::desktopEntryFromPid(uint pid) @@ -66,8 +62,8 @@ } const QByteArray key = line.left(equalsIdx); - const QByteArray value = line.mid(equalsIdx + 1); if (key == bamfDesktopFileHint) { + const QByteArray value = line.mid(equalsIdx + 1); return value; } } @@ -77,7 +73,7 @@ QModelIndex Utils::mapToModel(const QModelIndex &idx, const QAbstractItemModel *sourceModel) { - // KModelIndexProxyMapper can only map diferent indices to a single source + // KModelIndexProxyMapper can only map different indices to a single source // but we have the other way round, a single index that splits into different source models QModelIndex resolvedIdx = idx; while (resolvedIdx.isValid() && resolvedIdx.model() != sourceModel) { diff --git a/libtaskmanager/CMakeLists.txt b/libtaskmanager/CMakeLists.txt --- a/libtaskmanager/CMakeLists.txt +++ b/libtaskmanager/CMakeLists.txt @@ -49,7 +49,6 @@ KF5::I18n KF5::KIOCore KF5::KIOWidgets - KF5::ProcessCore KF5::WaylandClient KF5::WindowSystem ) diff --git a/libtaskmanager/abstracttasksmodel.h b/libtaskmanager/abstracttasksmodel.h --- a/libtaskmanager/abstracttasksmodel.h +++ b/libtaskmanager/abstracttasksmodel.h @@ -91,6 +91,11 @@ itself. DO NOT use this for destructive actions such as closing the application. The intended use case is to try and (smartly) gather more information about the task when needed. */ + StackingOrder, /**< A window task's index in the window stacking order. Care must be + taken not to assume this index to be unique when iterating over + model contents due to the asynchronous nature of the windowing + system. */ + LastActivated, /**< The timestamp of the last time a task was the active task. */ }; Q_ENUM(AdditionalRoles) diff --git a/libtaskmanager/launchertasksmodel.cpp b/libtaskmanager/launchertasksmodel.cpp --- a/libtaskmanager/launchertasksmodel.cpp +++ b/libtaskmanager/launchertasksmodel.cpp @@ -162,7 +162,7 @@ const QString &menuId = service->menuId(); if (!menuId.isEmpty()) { - url = QUrl(QStringLiteral("applications:") + menuId); + url = QUrl(QLatin1String("applications:") + menuId); } } } @@ -356,7 +356,7 @@ } else if (role == IsOnAllVirtualDesktops) { return true; } else if (role == Activities) { - return QStringList(d->activitiesForLauncher[url].toList()); + return QStringList(d->activitiesForLauncher[url].values()); } return QVariant(); @@ -381,7 +381,7 @@ } else { serializedLauncher = - "[" + d->activitiesForLauncher[launcher].toList().join(",") + "]\n" + + "[" + d->activitiesForLauncher[launcher].values().join(",") + "]\n" + launcher.toString(); } @@ -536,7 +536,7 @@ // If the launcher is on all activities, return a null uuid return d->activitiesForLauncher.contains(url) - ? d->activitiesForLauncher[url].toList() + ? d->activitiesForLauncher[url].values() : QStringList { NULL_UUID }; } } diff --git a/libtaskmanager/startuptasksmodel.cpp b/libtaskmanager/startuptasksmodel.cpp --- a/libtaskmanager/startuptasksmodel.cpp +++ b/libtaskmanager/startuptasksmodel.cpp @@ -174,9 +174,8 @@ // Fall-through to menuId() handling below services = {service}; } else { - if (appId.endsWith(QLatin1String(".desktop"))) { - appId = appId.mid(appId.length() - 8); - } + // turn into KService desktop entry name + appId.chop(strlen(".desktop")); services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName)").arg(appId)); diff --git a/libtaskmanager/taskgroupingproxymodel.cpp b/libtaskmanager/taskgroupingproxymodel.cpp --- a/libtaskmanager/taskgroupingproxymodel.cpp +++ b/libtaskmanager/taskgroupingproxymodel.cpp @@ -771,20 +771,22 @@ if (sourceModel) { d->rebuildMap(); - connect(sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), - this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)), Qt::UniqueConnection); - connect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SLOT(sourceRowsInserted(QModelIndex,int,int)), Qt::UniqueConnection); - connect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), - this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)), Qt::UniqueConnection); - connect(sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), - this, SLOT(sourceRowsRemoved(QModelIndex,int,int)), Qt::UniqueConnection); - connect(sourceModel, SIGNAL(modelAboutToBeReset()), - this, SLOT(sourceModelAboutToBeReset()), Qt::UniqueConnection); - connect(sourceModel, SIGNAL(modelReset()), - this, SLOT(sourceModelReset()), Qt::UniqueConnection); - connect(sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), - this, SLOT(sourceDataChanged(QModelIndex,QModelIndex,QVector)), Qt::UniqueConnection); + using namespace std::placeholders; + auto dd = d.data(); + connect(sourceModel, &QSortFilterProxyModel::rowsAboutToBeInserted, + this, std::bind(&TaskGroupingProxyModel::Private::sourceRowsAboutToBeInserted, dd, _1, _2, _3)); + connect(sourceModel, &QSortFilterProxyModel::rowsInserted, + this, std::bind(&TaskGroupingProxyModel::Private::sourceRowsInserted, dd, _1, _2, _3)); + connect(sourceModel, &QSortFilterProxyModel::rowsAboutToBeRemoved, + this, std::bind(&TaskGroupingProxyModel::Private::sourceRowsAboutToBeRemoved, dd, _1, _2, _3)); + connect(sourceModel, &QSortFilterProxyModel::rowsRemoved, + this, std::bind(&TaskGroupingProxyModel::Private::sourceRowsRemoved, dd, _1, _2, _3)); + connect(sourceModel, &QSortFilterProxyModel::modelAboutToBeReset, + this, std::bind(&TaskGroupingProxyModel::Private::sourceModelAboutToBeReset, dd)); + connect(sourceModel, &QSortFilterProxyModel::modelReset, + this, std::bind(&TaskGroupingProxyModel::Private::sourceModelReset, dd)); + connect(sourceModel, &QSortFilterProxyModel::dataChanged, + this, std::bind(&TaskGroupingProxyModel::Private::sourceDataChanged, dd, _1, _2, _3)); } else { d->rowMap.clear(); } @@ -844,7 +846,7 @@ QStringList TaskGroupingProxyModel::blacklistedAppIds() const { - return d->blacklistedAppIds.toList(); + return d->blacklistedAppIds.values(); } void TaskGroupingProxyModel::setBlacklistedAppIds(const QStringList &list) @@ -875,7 +877,7 @@ QStringList TaskGroupingProxyModel::blacklistedLauncherUrls() const { - return d->blacklistedLauncherUrls.toList(); + return d->blacklistedLauncherUrls.values(); } void TaskGroupingProxyModel::setBlacklistedLauncherUrls(const QStringList &list) @@ -1009,13 +1011,26 @@ } else { const bool goalState = !index.data(AbstractTasksModel::IsMaximized).toBool(); - for (int i = 0; i < rowCount(index); ++i) { - const QModelIndex &child = index.child(i, 0); + QModelIndexList inStackingOrder; - if (child.data(AbstractTasksModel::IsMaximized).toBool() != goalState) { - d->abstractTasksSourceModel->requestToggleMaximized(mapToSource(child)); - } - } + for (int i = 0; i < rowCount(index); ++i) { + const QModelIndex &child = index.child(i, 0); + + if (child.data(AbstractTasksModel::IsMaximized).toBool() != goalState) { + inStackingOrder << mapToSource(child); + } + } + + std::sort(inStackingOrder.begin(), inStackingOrder.end(), + [](const QModelIndex &a, const QModelIndex &b) { + return (a.data(AbstractTasksModel::StackingOrder).toInt() + < b.data(AbstractTasksModel::StackingOrder).toInt()); + } + ); + + for (const QModelIndex &sourceChild : inStackingOrder) { + d->abstractTasksSourceModel->requestToggleMaximized(sourceChild); + } } } diff --git a/libtaskmanager/tasksmodel.h b/libtaskmanager/tasksmodel.h --- a/libtaskmanager/tasksmodel.h +++ b/libtaskmanager/tasksmodel.h @@ -301,7 +301,7 @@ * The sort mode used in sorting tasks. Defaults to SortAlpha. * * @see setSortMode - * @returns the curent sort mode. + * @returns the current sort mode. **/ SortMode sortMode() const; diff --git a/libtaskmanager/tasksmodel.cpp b/libtaskmanager/tasksmodel.cpp --- a/libtaskmanager/tasksmodel.cpp +++ b/libtaskmanager/tasksmodel.cpp @@ -203,7 +203,7 @@ // This is particularly useful in concert with taskmanagerrulesrc's SkipTaskbar // key, which is used to hide window tasks which update from bogus to useful // window metadata early in startup. The role change then coincides with positive - // app identication, which is when updateManualSortMap() becomes able to sort the + // app identification, which is when updateManualSortMap() becomes able to sort the // task adjacent to its launcher when required to do so. if (sortMode == SortManual && roles.contains(AbstractTasksModel::SkipTaskbar)) { updateManualSortMap(); @@ -930,7 +930,7 @@ case SortActivity: { // updateActivityTaskCounts() counts the number of window tasks on each // activity. This will sort tasks by comparing a cumulative score made - // up of the task counts for each acvtivity a task is assigned to, and + // up of the task counts for each activity a task is assigned to, and // otherwise fall through to alphabetical sorting. int leftScore = -1; int rightScore = -1; @@ -1719,7 +1719,7 @@ // Move launcher for launcher-backed task along with task if launchers // are not being kept separate. - // We don't need to resort again because the launcher is implictly hidden + // We don't need to resort again because the launcher is implicitly hidden // at this time. if (!idx.data(AbstractTasksModel::IsLauncher).toBool()) { const int launcherPos = d->launcherTasksModel->launcherPosition(launcherUrl); diff --git a/libtaskmanager/tasktools.cpp b/libtaskmanager/tasktools.cpp --- a/libtaskmanager/tasktools.cpp +++ b/libtaskmanager/tasktools.cpp @@ -32,9 +32,7 @@ #include #include #include - -#include -#include +#include #include @@ -68,13 +66,13 @@ if (uQuery.hasQueryItem(QLatin1String("skipTaskbar"))) { QString skipTaskbar(uQuery.queryItemValue(QLatin1String("skipTaskbar"))); - data.skipTaskbar = (skipTaskbar == QStringLiteral("true")); + data.skipTaskbar = (skipTaskbar == QLatin1String("true")); } } // applications: URLs are used to refer to applications by their KService::menuId // (i.e. .desktop file name) rather than the absolute path to a .desktop file. - if (url.scheme() == QStringLiteral("applications")) { + if (url.scheme() == QLatin1String("applications")) { const KService::Ptr service = KService::serviceByMenuId(url.path()); if (service && url.path() == service->menuId()) { @@ -96,7 +94,7 @@ const QString &menuId = service->menuId(); if (!menuId.isEmpty()) { - data.url = QUrl(QStringLiteral("applications:") + menuId); + data.url = QUrl(QLatin1String("applications:") + menuId); } } @@ -143,7 +141,7 @@ // Update with resolved URL. if (!menuId.isEmpty()) { - data.url = QUrl(QStringLiteral("applications:") + menuId); + data.url = QUrl(QLatin1String("applications:") + menuId); } else { data.url = QUrl::fromLocalFile(desktopFile); } @@ -177,7 +175,7 @@ // applications: URLs are used to refer to applications by their KService::menuId // (i.e. .desktop file name) rather than the absolute path to a .desktop file. if (!menuId.isEmpty()) { - data.url = QUrl(QStringLiteral("applications:") + menuId); + data.url = QUrl(QLatin1String("applications:") + menuId); } else { data.url = QUrl::fromLocalFile(service->entryPath()); } @@ -373,7 +371,7 @@ } // The appId looks like a path. - if (services.isEmpty() && appId.startsWith(QStringLiteral("/"))) { + if (services.isEmpty() && appId.startsWith(QLatin1String("/"))) { // Check if it's a path to a .desktop file. if (KDesktopFile::isDesktopFile(appId) && QFile::exists(appId)) { return QUrl::fromLocalFile(appId); @@ -505,17 +503,43 @@ return KService::List(); } - KSysGuard::Processes procs; - procs.updateOrAddProcess(pid); + // Read the BAMF_DESKTOP_FILE_HINT environment variable which contains the actual desktop file path for Snaps. + QFile environFile(QStringLiteral("/proc/%1/environ").arg(QString::number(pid))); + if (environFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + const QByteArray bamfDesktopFileHint = QByteArrayLiteral("BAMF_DESKTOP_FILE_HINT"); + + const auto lines = environFile.readAll().split('\0'); + for (const QByteArray &line : lines) { + const int equalsIdx = line.indexOf('='); + if (equalsIdx <= 0) { + continue; + } + + const QByteArray key = line.left(equalsIdx); + if (key == bamfDesktopFileHint) { + const QByteArray value = line.mid(equalsIdx + 1); + + KService::Ptr service = KService::serviceByDesktopPath(QString::fromUtf8(value)); + if (service) { + return {service}; + } + break; + } + } + } + + auto proc = KProcessList::processInfo(pid); + if (!proc.isValid()) { + return KService::List(); + } - KSysGuard::Process *proc = procs.getProcess(pid); - const QString &cmdLine = proc ? proc->command().simplified() : QString(); // proc->command has a trailing space??? + const QString cmdLine = proc.command(); if (cmdLine.isEmpty()) { return KService::List(); } - return servicesFromCmdLine(cmdLine, proc->name(), rulesConfig); + return servicesFromCmdLine(cmdLine, proc.name(), rulesConfig); } KService::List servicesFromCmdLine(const QString &_cmdLine, const QString &processName, @@ -544,7 +568,7 @@ if (services.isEmpty() && firstSpace > 0) { // Could not find with arguments, so try without ... - cmdLine = cmdLine.left(firstSpace); + cmdLine.truncate(firstSpace); services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine)); @@ -627,7 +651,7 @@ browserApp = htmlApp->storageId(); } } else if (browserApp.startsWith('!')) { - browserApp = browserApp.mid(1); + browserApp.remove(0, 1); } return browserApp; @@ -700,7 +724,7 @@ const QString &menuId = service->menuId(); if (!menuId.isEmpty()) { - resolvedUrl = QUrl(QStringLiteral("applications:") + menuId); + resolvedUrl = QUrl(QLatin1String("applications:") + menuId); resolvedUrl.setQuery(url.query()); } } @@ -780,7 +804,7 @@ // applications: URLs are used to refer to applications by their KService::menuId // (i.e. .desktop file name) rather than the absolute path to a .desktop file. - if (appData.url.scheme() == QStringLiteral("applications")) { + if (appData.url.scheme() == QLatin1String("applications")) { service = KService::serviceByMenuId(appData.url.path()); } else if (appData.url.scheme() == QLatin1String("preferred")) { const KService::Ptr service = KService::serviceByStorageId(defaultApplication(appData.url)); diff --git a/libtaskmanager/waylandtasksmodel.h b/libtaskmanager/waylandtasksmodel.h --- a/libtaskmanager/waylandtasksmodel.h +++ b/libtaskmanager/waylandtasksmodel.h @@ -187,7 +187,7 @@ /** * Request moving the window at the given index to the specified activities * - * FIXME: This currently does nothing as activities is not implementated in kwin/kwayland + * FIXME: This currently does nothing as activities is not implemented in kwin/kwayland * * @param index An index in this window tasks model. * @param desktop A virtual desktop number. diff --git a/libtaskmanager/xwindowsystemeventbatcher.h b/libtaskmanager/xwindowsystemeventbatcher.h --- a/libtaskmanager/xwindowsystemeventbatcher.h +++ b/libtaskmanager/xwindowsystemeventbatcher.h @@ -24,6 +24,7 @@ #include #include +#include /* * Relay class for KWindowSystem events that batches updates diff --git a/libtaskmanager/xwindowtasksmodel.cpp b/libtaskmanager/xwindowtasksmodel.cpp --- a/libtaskmanager/xwindowtasksmodel.cpp +++ b/libtaskmanager/xwindowtasksmodel.cpp @@ -65,6 +65,8 @@ QHash appDataCache; QHash delegateGeometries; QSet usingFallbackIcon; + QHash lastActivated; + QList cachedStackingOrder; WId activeWindow = -1; KSharedConfig::Ptr rulesConfig; KDirWatch *configWatcher = nullptr; @@ -120,6 +122,8 @@ AbstractTasksModel::SkipTaskbar}); }; + cachedStackingOrder = KWindowSystem::stackingOrder(); + sycocaChangeTimer.setSingleShot(true); sycocaChangeTimer.setInterval(100); @@ -177,6 +181,7 @@ [this](WId window) { const WId oldActiveWindow = activeWindow; activeWindow = window; + lastActivated[activeWindow] = QTime::currentTime(); int row = windows.indexOf(oldActiveWindow); @@ -192,6 +197,14 @@ } ); + QObject::connect(KWindowSystem::self(), &KWindowSystem::stackingOrderChanged, q, + [this]() { + cachedStackingOrder = KWindowSystem::stackingOrder(); + q->dataChanged(q->index(0, 0), q->index(q->rowCount() - 1, 0), + QVector{StackingOrder}); + } + ); + activeWindow = KWindowSystem::activeWindow(); // Add existing windows. @@ -255,8 +268,9 @@ transientsDemandingAttention.remove(window); delete windowInfoCache.take(window); appDataCache.remove(window); - usingFallbackIcon.remove(window); delegateGeometries.remove(window); + usingFallbackIcon.remove(window); + lastActivated.remove(window); q->endRemoveRows(); } else { // Could be a transient. // Removing a transient might change the demands attention state of the leader. @@ -513,26 +527,43 @@ } return windowUrlFromMetadata(info->windowClassClass(), - NETWinInfo(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMPid, NET::Properties2()).pid(), + info->pid(), rulesConfig, info->windowClassName()); } QUrl XWindowTasksModel::Private::launcherUrl(WId window, bool encodeFallbackIcon) { const AppData &data = appData(window); - if (!encodeFallbackIcon || !data.icon.name().isEmpty()) { - return data.url; - } QUrl url = data.url; + if (!encodeFallbackIcon || !data.icon.name().isEmpty()) { + return url; + } // Forego adding the window icon pixmap if the URL is otherwise empty. if (!url.isValid()) { return QUrl(); } - const QPixmap pixmap = KWindowSystem::icon(window, KIconLoader::SizeLarge, KIconLoader::SizeLarge, false); + // Only serialize pixmap data if the window pixmap is actually being used. + // QIcon::name() used above only returns a themed icon name but nothing when + // the icon was created using an absolute path, as can be the case with, e.g. + // containerized apps. + if (!usingFallbackIcon.contains(window)) { + return url; + } + + QPixmap pixmap; + + if (!data.icon.isNull()) { + pixmap = data.icon.pixmap(KIconLoader::SizeLarge); + } + + if (pixmap.isNull()) { + pixmap = KWindowSystem::icon(window, KIconLoader::SizeLarge, KIconLoader::SizeLarge, false); + } + if (pixmap.isNull()) { return data.url; } @@ -617,7 +648,7 @@ } else if (role == IsMinimized) { return d->windowInfo(window)->isMinimized(); } else if (role == IsKeepAbove) { - return d->windowInfo(window)->hasState(NET::StaysOnTop); + return d->windowInfo(window)->hasState(NET::KeepAbove); } else if (role == IsKeepBelow) { return d->windowInfo(window)->hasState(NET::KeepBelow); } else if (role == IsFullScreenable) { @@ -653,6 +684,12 @@ return d->windowInfo(window)->hasState(NET::SkipPager); } else if (role == AppPid) { return d->windowInfo(window)->pid(); + } else if (role == StackingOrder) { + return d->cachedStackingOrder.indexOf(window); + } else if (role == LastActivated) { + if (d->lastActivated.contains(window)) { + return d->lastActivated.value(window); + } } return QVariant(); @@ -849,10 +886,10 @@ NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); - if (info->hasState(NET::StaysOnTop)) { - ni.setState(NET::States(), NET::StaysOnTop); + if (info->hasState(NET::KeepAbove)) { + ni.setState(NET::States(), NET::KeepAbove); } else { - ni.setState(NET::StaysOnTop, NET::StaysOnTop); + ni.setState(NET::KeepAbove, NET::KeepAbove); } } diff --git a/login-sessions/install-sessions.sh.cmake b/login-sessions/install-sessions.sh.cmake --- a/login-sessions/install-sessions.sh.cmake +++ b/login-sessions/install-sessions.sh.cmake @@ -1,7 +1,7 @@ #!/bin/sh set -e -install @CMAKE_CURRENT_BINARY_DIR@/plasmax11-dev.desktop /usr/share/xsessions/ -install @CMAKE_CURRENT_BINARY_DIR@/plasmawayland-dev.desktop /usr/share/wayland-sessions/ +sudo install @CMAKE_CURRENT_BINARY_DIR@/plasmax11-dev.desktop /usr/share/xsessions/ +sudo install @CMAKE_CURRENT_BINARY_DIR@/plasmawayland-dev.desktop /usr/share/wayland-sessions/ install @CMAKE_BINARY_DIR@/prefix.sh @CMAKE_INSTALL_FULL_LIBEXECDIR@/plasma-dev-prefix.sh install @CMAKE_CURRENT_BINARY_DIR@/startplasma-dev.sh @CMAKE_INSTALL_FULL_LIBEXECDIR@ diff --git a/login-sessions/plasma.desktop.cmake b/login-sessions/plasma.desktop.cmake --- a/login-sessions/plasma.desktop.cmake +++ b/login-sessions/plasma.desktop.cmake @@ -5,6 +5,7 @@ DesktopNames=KDE Name=Plasma Name[ar]=بلازما +Name[ast]=Plasma Name[bs]=Plazma Name[ca]=Plasma Name[ca@valencia]=Plasma @@ -27,6 +28,7 @@ Name[ja]=プラズマ Name[ko]=Plasma Name[lt]=Plasma +Name[lv]=Plasma Name[nb]=Plasma Name[nds]=Plasma Name[nl]=Plasma @@ -43,13 +45,15 @@ Name[sr@ijekavianlatin]=Plasma Name[sr@latin]=Plasma Name[sv]=Plasma +Name[tg]=Плазма Name[tr]=Plama Name[uk]=Плазма Name[x-test]=xxPlasmaxx Name[zh_CN]=Plasma Name[zh_TW]=Plasma Comment=Plasma by KDE Comment[ar]=بلازما كدي +Comment[ast]=Plasma por KDE Comment[bs]=Plazma od strane KDe Comment[ca]=Plasma, creat per la comunitat KDE Comment[ca@valencia]=Plasma, creat per la comunitat KDE @@ -71,7 +75,7 @@ Comment[it]=Plasma di KDE Comment[ja]=Plasma by KDE Comment[ko]=KDE Plasma -Comment[lt]=Plasmą sukūrė KDE +Comment[lt]=Plasmą pagal KDE Comment[nb]=Plasma av KDE Comment[nds]=Plasma vun KDE Comment[nl]=Plasma door KDE diff --git a/login-sessions/plasmawayland-dev.desktop.cmake b/login-sessions/plasmawayland-dev.desktop.cmake --- a/login-sessions/plasmawayland-dev.desktop.cmake +++ b/login-sessions/plasmawayland-dev.desktop.cmake @@ -1,21 +1,35 @@ [Desktop Entry] Exec=dbus-run-session @CMAKE_INSTALL_FULL_LIBEXECDIR@/startplasma-dev.sh -wayland DesktopNames=KDE -Name=Plasma (Development ${CMAKE_INSTALL_FULL_BINDIR}) -Name[ca]=Plasma (Desenvolupament ${CMAKE_INSTALL_FULL_BINDIR}) -Name[es]=Plasma (Desarrollo ${CMAKE_INSTALL_FULL_BINDIR}) -Name[eu]=Plasma (Garapena ${CMAKE_INSTALL_FULL_BINDIR}) -Name[it]=Plasma (Sviluppo ${CMAKE_INSTALL_FULL_BINDIR}) -Name[nl]=Plasma (Ontwikkeling ${CMAKE_INSTALL_FULL_BINDIR}) -Name[nn]=Plasma (utvikling ${CMAKE_INSTALL_FULL_BINDIR}) -Name[pt]=Plasma (Desenvolvimento ${CMAKE_INSTALL_FULL_BINDIR}) -Name[pt_BR]=Plasma (Desenvolvimento ${CMAKE_INSTALL_FULL_BINDIR}) -Name[sv]=Plasma (utveckling ${CMAKE_INSTALL_FULL_BINDIR}) -Name[uk]=Пзазма (Розробка ${CMAKE_INSTALL_FULL_BINDIR}) -Name[x-test]=xxPlasma (Development ${CMAKE_INSTALL_FULL_BINDIR})xx -Name[zh_TW]=Plasma(開發版本 ${CMAKE_INSTALL_FULL_BINDIR}) +Name=Plasma (Development, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[ast]=Plasma (Desendolcu, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[ca]=Plasma (Desenvolupament, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[de]=Plasma (Development, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[en_GB]=Plasma (Development, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[es]=Plasma (Desarrollo, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[et]=Plasma (arendus, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[eu]=Plasma (Garapena, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[fr]=Plasma (Développement, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[gl]=Plasma (desenvolvemento, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[hu]=Plasma (Fejlesztői verzió, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[id]=Plasma (Development, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[it]=Plasma (Sviluppo, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[lt]=Plasma (Plėtojimas, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[nl]=Plasma (Ontwikkeling, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[nn]=Plasma (utvikling, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[pl]=Plazma (Rozwój , Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[pt]=Plasma (Desenvolvimento, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[pt_BR]=Plasma (Desenvolvimento, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[ru]=Plasma (разрабатываемая версия, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[sk]=Plasma (Development, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[sv]=Plasma (utveckling, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[uk]=Пзазма (Розробка, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[x-test]=xxPlasma (Development, Wayland ${CMAKE_INSTALL_FULL_BINDIR})xx +Name[zh_CN]=Plasma (Development, Wayland ${CMAKE_INSTALL_FULL_BINDIR}) +Name[zh_TW]=Plasma (開發版本,Wayland ${CMAKE_INSTALL_FULL_BINDIR}) Comment=Plasma by KDE Comment[ar]=بلازما كدي +Comment[ast]=Plasma por KDE Comment[bs]=Plazma od strane KDe Comment[ca]=Plasma, creat per la comunitat KDE Comment[ca@valencia]=Plasma, creat per la comunitat KDE @@ -37,7 +51,7 @@ Comment[it]=Plasma di KDE Comment[ja]=Plasma by KDE Comment[ko]=KDE Plasma -Comment[lt]=Plasmą sukūrė KDE +Comment[lt]=Plasmą pagal KDE Comment[nb]=Plasma av KDE Comment[nds]=Plasma vun KDE Comment[nl]=Plasma door KDE diff --git a/login-sessions/plasmawayland.desktop.cmake b/login-sessions/plasmawayland.desktop.cmake --- a/login-sessions/plasmawayland.desktop.cmake +++ b/login-sessions/plasmawayland.desktop.cmake @@ -2,53 +2,37 @@ Exec=dbus-run-session ${CMAKE_INSTALL_FULL_BINDIR}/startplasma-wayland TryExec=${CMAKE_INSTALL_FULL_BINDIR}/startplasma-wayland DesktopNames=KDE -Name=Plasma -Name[ar]=بلازما -Name[bs]=Plazma -Name[ca]=Plasma -Name[ca@valencia]=Plasma -Name[cs]=Plasma -Name[da]=Plasma -Name[de]=Plasma -Name[el]=Plasma -Name[en_GB]=Plasma -Name[es]=Plasma -Name[et]=Plasma -Name[eu]=Plasma -Name[fi]=Plasma -Name[fr]=Plasma -Name[gl]=Plasma -Name[he]=פלזמה -Name[hu]=Plasma -Name[id]=Plasma -Name[is]=Plasma -Name[it]=Plasma -Name[ja]=プラズマ -Name[ko]=Plasma -Name[lt]=Plasma -Name[nb]=Plasma -Name[nds]=Plasma -Name[nl]=Plasma -Name[nn]=Plasma -Name[pa]=ਪਲਾਜ਼ਮਾ -Name[pl]=Plazma -Name[pt]=Plasma -Name[pt_BR]=Plasma -Name[ru]=Plasma -Name[sk]=Plasma -Name[sl]=Plasma -Name[sr]=Плазма -Name[sr@ijekavian]=Плазма -Name[sr@ijekavianlatin]=Plasma -Name[sr@latin]=Plasma -Name[sv]=Plasma -Name[tr]=Plama -Name[uk]=Плазма -Name[x-test]=xxPlasmaxx -Name[zh_CN]=Plasma -Name[zh_TW]=Plasma +Name=Plasma (Wayland) +Name[ast]=Plasma (Wayland) +Name[ca]=Plasma (Wayland) +Name[cs]=Plasma (Wayland) +Name[de]=Plasma (Wayland) +Name[en_GB]=Plasma (Wayland) +Name[es]=Plasma (Wayland) +Name[et]=Plasma (Wayland) +Name[eu]=Plasma (Wayland) +Name[fr]=Plasma (Wayland) +Name[gl]=Plasma (Wayland) +Name[hu]=Plasma (Wayland) +Name[id]=Plasma (Wayland) +Name[it]=Plasma (Wayland) +Name[lt]=Plasma (Wayland) +Name[lv]=Plasma (Wayland) +Name[nl]=Plasma (Wayland) +Name[nn]=Plasma (Wayland) +Name[pl]=Plazma (Wayland) +Name[pt]=Plasma (Wayland) +Name[pt_BR]=Plasma (Wayland) +Name[ru]=Plasma (Wayland) +Name[sk]=Plasma (Wayland) +Name[sv]=Plasma (Wayland) +Name[uk]=Плазма (Wayland) +Name[x-test]=xxPlasma (Wayland)xx +Name[zh_CN]=Plasma (Wayland) +Name[zh_TW]=Plasma (Wayland) Comment=Plasma by KDE Comment[ar]=بلازما كدي +Comment[ast]=Plasma por KDE Comment[bs]=Plazma od strane KDe Comment[ca]=Plasma, creat per la comunitat KDE Comment[ca@valencia]=Plasma, creat per la comunitat KDE @@ -70,7 +54,7 @@ Comment[it]=Plasma di KDE Comment[ja]=Plasma by KDE Comment[ko]=KDE Plasma -Comment[lt]=Plasmą sukūrė KDE +Comment[lt]=Plasmą pagal KDE Comment[nb]=Plasma av KDE Comment[nds]=Plasma vun KDE Comment[nl]=Plasma door KDE diff --git a/login-sessions/plasmax11-dev.desktop.cmake b/login-sessions/plasmax11-dev.desktop.cmake --- a/login-sessions/plasmax11-dev.desktop.cmake +++ b/login-sessions/plasmax11-dev.desktop.cmake @@ -2,21 +2,35 @@ Type=XSession Exec=@CMAKE_INSTALL_FULL_LIBEXECDIR@/startplasma-dev.sh -x11 DesktopNames=KDE -Name=Plasma (Development ${CMAKE_INSTALL_FULL_BINDIR}) -Name[ca]=Plasma (Desenvolupament ${CMAKE_INSTALL_FULL_BINDIR}) -Name[es]=Plasma (Desarrollo ${CMAKE_INSTALL_FULL_BINDIR}) -Name[eu]=Plasma (Garapena ${CMAKE_INSTALL_FULL_BINDIR}) -Name[it]=Plasma (Sviluppo ${CMAKE_INSTALL_FULL_BINDIR}) -Name[nl]=Plasma (Ontwikkeling ${CMAKE_INSTALL_FULL_BINDIR}) -Name[nn]=Plasma (utvikling ${CMAKE_INSTALL_FULL_BINDIR}) -Name[pt]=Plasma (Desenvolvimento ${CMAKE_INSTALL_FULL_BINDIR}) -Name[pt_BR]=Plasma (Desenvolvimento ${CMAKE_INSTALL_FULL_BINDIR}) -Name[sv]=Plasma (utveckling ${CMAKE_INSTALL_FULL_BINDIR}) -Name[uk]=Пзазма (Розробка ${CMAKE_INSTALL_FULL_BINDIR}) -Name[x-test]=xxPlasma (Development ${CMAKE_INSTALL_FULL_BINDIR})xx -Name[zh_TW]=Plasma(開發版本 ${CMAKE_INSTALL_FULL_BINDIR}) +Name=Plasma (Development, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[ast]=Plasma (Desendolcu, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[ca]=Plasma (Desenvolupament, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[de]=Plasma (Development, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[en_GB]=Plasma (Development, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[es]=Plasma (Desarrollo, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[et]=Plasma (arendus, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[eu]=Plasma (Garapena, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[fr]=Plasma (Développement, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[gl]=Plasma (desenvolvemento, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[hu]=Plasma (Fejlesztői verzió, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[id]=Plasma (Development, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[it]=Plasma (Sviluppo, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[lt]=Plasma (Plėtojimas, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[nl]=Plasma (Ontwikkeling, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[nn]=Plasma (utvikling, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[pl]=Plazma (Rozwój , X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[pt]=Plasma (Desenvolvimento, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[pt_BR]=Plasma (Desenvolvimento, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[ru]=Plasma (разрабатываемая версия, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[sk]=Plasma (Development, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[sv]=Plasma (utveckling, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[uk]=Пзазма (Розробка, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[x-test]=xxPlasma (Development, X11 ${CMAKE_INSTALL_FULL_BINDIR})xx +Name[zh_CN]=Plasma (Development, X11 ${CMAKE_INSTALL_FULL_BINDIR}) +Name[zh_TW]=Plasma (開發版本,X11 ${CMAKE_INSTALL_FULL_BINDIR}) Comment=Plasma by KDE Comment[ar]=بلازما كدي +Comment[ast]=Plasma por KDE Comment[bs]=Plazma od strane KDe Comment[ca]=Plasma, creat per la comunitat KDE Comment[ca@valencia]=Plasma, creat per la comunitat KDE @@ -38,7 +52,7 @@ Comment[it]=Plasma di KDE Comment[ja]=Plasma by KDE Comment[ko]=KDE Plasma -Comment[lt]=Plasmą sukūrė KDE +Comment[lt]=Plasmą pagal KDE Comment[nb]=Plasma av KDE Comment[nds]=Plasma vun KDE Comment[nl]=Plasma door KDE diff --git a/logout-greeter/CMakeLists.txt b/logout-greeter/CMakeLists.txt --- a/logout-greeter/CMakeLists.txt +++ b/logout-greeter/CMakeLists.txt @@ -15,7 +15,6 @@ Qt5::Quick Qt5::X11Extras KF5::Declarative - KF5::IconThemes KF5::I18n KF5::Package KF5::QuickAddons diff --git a/logout-greeter/shutdowndlg.cpp b/logout-greeter/shutdowndlg.cpp --- a/logout-greeter/shutdowndlg.cpp +++ b/logout-greeter/shutdowndlg.cpp @@ -46,7 +46,6 @@ #include #include -#include #include #include #include @@ -127,7 +126,7 @@ context->setContextProperty(QStringLiteral("spdMethods"), mapSpdMethods); context->setContextProperty(QStringLiteral("canLogout"), KAuthorized::authorize(QStringLiteral("logout"))); - // Trying to access a non-existant context property throws an error, always create the property and then update it later + // Trying to access a non-existent context property throws an error, always create the property and then update it later context->setContextProperty("rebootToFirmwareSetup", false); QDBusMessage message = QDBusMessage::createMethodCall(s_login1Service, s_login1Path, s_dbusPropertiesInterface, QStringLiteral("Get")); diff --git a/lookandfeel/contents/components/UserDelegate.qml b/lookandfeel/contents/components/UserDelegate.qml --- a/lookandfeel/contents/components/UserDelegate.qml +++ b/lookandfeel/contents/components/UserDelegate.qml @@ -41,7 +41,7 @@ property int fontSize: config.fontSize signal clicked() - property real faceSize: Math.min(width, height - usernameDelegate.height - units.smallSpacing) + property real faceSize: units.gridUnit * 7 opacity: isCurrent ? 1.0 : 0.5 @@ -118,7 +118,7 @@ property var colorBorder: PlasmaCore.ColorScope.textColor - //draw a circle with an antialised border + //draw a circle with an antialiased border //innerRadius = size of the inner circle with contents //outerRadius = size of the border //blend = area to blend between two colours diff --git a/lookandfeel/contents/components/WallpaperFader.qml b/lookandfeel/contents/components/WallpaperFader.qml --- a/lookandfeel/contents/components/WallpaperFader.qml +++ b/lookandfeel/contents/components/WallpaperFader.qml @@ -38,6 +38,8 @@ property real factor: 0 readonly property bool lightBackground: Math.max(PlasmaCore.ColorScope.backgroundColor.r, PlasmaCore.ColorScope.backgroundColor.g, PlasmaCore.ColorScope.backgroundColor.b) > 0.5 + property bool alwaysShowClock: typeof config === "undefined" || config.alwaysShowClock === true + Behavior on factor { NumberAnimation { target: wallpaperFader @@ -119,6 +121,10 @@ target: clock.shadow opacity: 0 } + PropertyChanges { + target: clock + opacity: 1 + } }, State { name: "off" @@ -136,46 +142,34 @@ } PropertyChanges { target: clock.shadow - opacity: 1 + opacity: wallpaperFader.alwaysShowClock ? 1 : 0 + } + PropertyChanges { + target: clock + opacity: wallpaperFader.alwaysShowClock ? 1 : 0 } } ] transitions: [ Transition { from: "off" to: "on" //Note: can't use animators as they don't play well with parallelanimations - ParallelAnimation { - NumberAnimation { - target: mainStack - property: "opacity" - duration: units.longDuration - easing.type: Easing.InOutQuad - } - NumberAnimation { - target: footer - property: "opacity" - duration: units.longDuration - easing.type: Easing.InOutQuad - } + NumberAnimation { + targets: [mainStack, footer, clock] + property: "opacity" + duration: units.longDuration + easing.type: Easing.InOutQuad } }, Transition { from: "on" to: "off" - ParallelAnimation { - NumberAnimation { - target: mainStack - property: "opacity" - duration: 500 - easing.type: Easing.InOutQuad - } - NumberAnimation { - target: footer - property: "opacity" - duration: 500 - easing.type: Easing.InOutQuad - } + NumberAnimation { + targets: [mainStack, footer, clock] + property: "opacity" + duration: 500 + easing.type: Easing.InOutQuad } } ] diff --git a/lookandfeel/contents/lockscreen/LockScreenUi.qml b/lookandfeel/contents/lockscreen/LockScreenUi.qml --- a/lookandfeel/contents/lockscreen/LockScreenUi.qml +++ b/lookandfeel/contents/lockscreen/LockScreenUi.qml @@ -30,9 +30,11 @@ PlasmaCore.ColorScope { + id: lockScreenUi // If we're using software rendering, draw outlines instead of shadows // See https://bugs.kde.org/show_bug.cgi?id=398317 readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software + readonly property bool lightBackground: Math.max(PlasmaCore.ColorScope.backgroundColor.r, PlasmaCore.ColorScope.backgroundColor.g, PlasmaCore.ColorScope.backgroundColor.b) > 0.5 colorGroup: PlasmaCore.Theme.ComplementaryColorGroup @@ -55,9 +57,20 @@ } } + SessionManagement { + id: sessionManagement + } + + Connections { + target: sessionManagement + onAboutToSuspend: { + mainBlock.mainPasswordBox.text = ""; + } + } + SessionsModel { id: sessionsModel - showNewSessionEntry: true + showNewSessionEntry: false } PlasmaCore.DataSource { @@ -175,7 +188,7 @@ radius: 6 samples: 14 spread: 0.3 - color: "black" // matches Breeze window decoration and desktopcontainment + color: lockScreenUi.lightBackground ? PlasmaCore.ColorScope.backgroundColor : "black" // black matches Breeze window decoration and desktopcontainment Behavior on opacity { OpacityAnimator { duration: 1000 @@ -249,9 +262,23 @@ ActionButton { text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch User") iconSource: "system-switch-user" - onClicked: mainStack.push(switchSessionPage) - // the current session isn't listed in the model, hence a check for greater than zero, not one - visible: (sessionsModel.count > 0 || sessionsModel.canStartNewSession) && sessionsModel.canSwitchUser + onClicked: { + // If there are no existing sessions to switch to, create a new one instead + if (((sessionsModel.showNewSessionEntry && sessionsModel.count === 1) || + (!sessionsModel.showNewSessionEntry && sessionsModel.count === 0)) && + sessionsModel.canSwitchUser) { + mainStack.pop({immediate:true}) + sessionsModel.startNewSession(true /* lock the screen too */) + lockScreenRoot.state = '' + } else { + mainStack.push(switchSessionPage) + } + } + visible: sessionsModel.canStartNewSession && sessionsModel.canSwitchUser + //Button gets cut off on smaller displays without this. + anchors{ + verticalCenter: parent.top + } } ] @@ -265,9 +292,17 @@ Component.onCompleted: { if (defaultToSwitchUser) { //context property - mainStack.push({ - item: switchSessionPage, - immediate: true}); + // If we are in the only session, then going to the session switcher is + // a pointless extra step; instead create a new session immediately + if (((sessionsModel.showNewSessionEntry && sessionsModel.count === 1) || + (!sessionsModel.showNewSessionEntry && sessionsModel.count === 0)) && + sessionsModel.canStartNewSession) { + sessionsModel.startNewSession(true /* lock the screen too */) + } else { + mainStack.push({ + item: switchSessionPage, + immediate: true}); + } } } } @@ -415,19 +450,40 @@ Keys.onReturnPressed: initSwitchSession() Keys.onEscapePressed: mainStack.pop() - PlasmaComponents.Button { + ColumnLayout { Layout.fillWidth: true - font.pointSize: theme.defaultFont.pointSize + 1 - // the magic "-1" vtNumber indicates the "New Session" entry - text: userListCurrentModelData.vtNumber === -1 ? i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Start New Session") : i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch Session") - onClicked: initSwitchSession() + spacing: units.largeSpacing + + PlasmaComponents.Button { + Layout.fillWidth: true + font.pointSize: theme.defaultFont.pointSize + 1 + text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch to This Session") + onClicked: initSwitchSession() + visible: sessionsModel.count > 0 + } + + PlasmaComponents.Button { + Layout.fillWidth: true + font.pointSize: theme.defaultFont.pointSize + 1 + text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Start New Session") + onClicked: { + mainStack.pop({immediate:true}) + sessionsModel.startNewSession(true /* lock the screen too */) + lockScreenRoot.state = '' + } + } } + actionItems: [ ActionButton { iconSource: "go-previous" text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Back") onClicked: mainStack.pop() + //Button gets cut off on smaller displays without this. + anchors{ + verticalCenter: parent.top + } } ] } diff --git a/lookandfeel/contents/lockscreen/MediaControls.qml b/lookandfeel/contents/lockscreen/MediaControls.qml --- a/lookandfeel/contents/lockscreen/MediaControls.qml +++ b/lookandfeel/contents/lockscreen/MediaControls.qml @@ -98,8 +98,9 @@ Layout.preferredWidth: height Layout.fillHeight: true asynchronous: true + fillMode: Image.PreserveAspectFit source: mpris2Source.albumArt - sourceSize: Qt.size(width, height) + sourceSize.height: height visible: status === Image.Loading || status === Image.Ready } @@ -119,6 +120,7 @@ text: mpris2Source.track || i18nd("plasma_lookandfeel_org.kde.lookandfeel", "No media playing") textFormat: Text.PlainText font.pointSize: theme.defaultFont.pointSize + 1 + maximumLineCount: 1 } PlasmaExtras.DescriptiveLabel { @@ -129,6 +131,7 @@ text: mpris2Source.artist || mpris2Source.identity || "" textFormat: Text.PlainText font.pointSize: theme.smallestFont.pointSize + 1 + maximumLineCount: 1 } } diff --git a/lookandfeel/contents/lockscreen/config.qml b/lookandfeel/contents/lockscreen/config.qml --- a/lookandfeel/contents/lockscreen/config.qml +++ b/lookandfeel/contents/lockscreen/config.qml @@ -1,23 +1,44 @@ -import QtQuick 2.4 -import QtQuick.Controls 2.0 +import QtQuick 2.5 +import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.1 -import org.kde.plasma.core 2.0 as Plasmacore - -RowLayout { +ColumnLayout { + property alias cfg_alwaysShowClock: alwaysClock.checked property alias cfg_showMediaControls: showMediaControls.checked - spacing: units.largeSpacing / 2 + spacing: 0 - Label { - Layout.minimumWidth: formAlignment - units.largeSpacing //to match wallpaper config... - horizontalAlignment: Text.AlignRight - text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Show media controls:") - } - CheckBox { - id: showMediaControls + RowLayout { + spacing: units.largeSpacing / 2 + + QQC2.Label { + Layout.minimumWidth: formAlignment - units.largeSpacing //to match wallpaper config... + horizontalAlignment: Text.AlignRight + text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Clock:") + } + QQC2.CheckBox { + id: alwaysClock + text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "verb, to show something", "Always show") + } + Item { + Layout.fillWidth: true + } } - Item { - Layout.fillWidth: true + + RowLayout { + spacing: units.largeSpacing / 2 + + QQC2.Label { + Layout.minimumWidth: formAlignment - units.largeSpacing //to match wallpaper config... + horizontalAlignment: Text.AlignRight + text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Media controls:") + } + QQC2.CheckBox { + id: showMediaControls + text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "verb, to show something", "Show") + } + Item { + Layout.fillWidth: true + } } } diff --git a/lookandfeel/contents/lockscreen/config.xml b/lookandfeel/contents/lockscreen/config.xml --- a/lookandfeel/contents/lockscreen/config.xml +++ b/lookandfeel/contents/lockscreen/config.xml @@ -6,6 +6,10 @@ + + + true + true diff --git a/lookandfeel/contents/logout/Logout.qml b/lookandfeel/contents/logout/Logout.qml --- a/lookandfeel/contents/logout/Logout.qml +++ b/lookandfeel/contents/logout/Logout.qml @@ -117,7 +117,7 @@ onClicked: root.cancelRequested() } UserDelegate { - width: units.iconSizes.enormous + width: units.gridUnit * 7 height: width nameFontSize: theme.defaultFont.pointSize + 2 anchors { diff --git a/lookandfeel/contents/runcommand/RunCommand.qml b/lookandfeel/contents/runcommand/RunCommand.qml --- a/lookandfeel/contents/runcommand/RunCommand.qml +++ b/lookandfeel/contents/runcommand/RunCommand.qml @@ -77,6 +77,32 @@ : i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Textfield placeholder text", "Search...") + PlasmaComponents.BusyIndicator { + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + margins: units.smallSpacing + rightMargin: height + } + + Behavior on opacity { + OpacityAnimator { + duration: units.longDuration + easing.type: Easing.InOutQuad + } + } + + Timer { + id: queryTimer + running: results.querying + interval: 500 + } + + opacity: !queryTimer.running && results.querying ? 1 : 0 + visible: opacity > 0 + running: visible + } function move_up() { if (length === 0) { root.showHistory = true; @@ -244,8 +270,8 @@ currentIndex = 0; } } - Keys.onReturnPressed: runCurrentIndex() - Keys.onEnterPressed: runCurrentIndex() + Keys.onReturnPressed: runCurrentIndex(event) + Keys.onEnterPressed: runCurrentIndex(event) Keys.onTabPressed: { if (currentIndex == listView.count-1) { @@ -276,9 +302,16 @@ Keys.onUpPressed: decrementCurrentIndex() Keys.onDownPressed: incrementCurrentIndex() - function runCurrentIndex() { + function runCurrentIndex(event) { var entry = runnerWindow.history[currentIndex] if (entry) { + // If user presses Shift+Return to invoke an action, invoke the first runner action + if (event && event.modifiers === Qt.ShiftModifier + && currentItem.additionalActions && currentItem.additionalActions.length > 0) { + runAction(0); + return + } + queryField.text = entry queryField.forceActiveFocus(); } diff --git a/lookandfeel/metadata.desktop b/lookandfeel/metadata.desktop --- a/lookandfeel/metadata.desktop +++ b/lookandfeel/metadata.desktop @@ -1,17 +1,22 @@ [Desktop Entry] Comment=Breeze by the KDE VDG -Comment[ca]=Brisa pel VDG del KDE -Comment[ca@valencia]=Brisa pel VDG del KDE +Comment[ca]=Brisa, creat pel VDG del KDE +Comment[ca@valencia]=Brisa per a VDG de KDE +Comment[cs]=Breeze od KDE VDG Comment[da]=Breeze af KDE VDG +Comment[de]=Breeze der KDE VG Comment[en_GB]=Breeze by the KDE VDG Comment[es]=Brisa por KDE VDG +Comment[et]=Breeze KDE VDG-lt Comment[eu]=Breeze KDEren VDGk egina Comment[fi]=Breeze KDE VDG:ltä Comment[fr]=Breeze, par KDE VDG Comment[gl]=Breeze de KDE VDG +Comment[hu]=Breeze a KDE VDG-től Comment[id]=Breeze oleh KDE VDG Comment[it]=Brezza a cura del VDG di KDE Comment[ko]=KDE 시각 디자인 그룹에서 제작한 Breeze +Comment[lt]=Breeze pagal KDE VDG Comment[nl]=Breeze door de KDE VDG Comment[nn]=Breeze frå KDE VDG Comment[pl]=Bryza autorstwa KDE VDG @@ -25,10 +30,11 @@ Comment[zh_CN]=微风,由 KDE VDG 创作 Comment[zh_TW]=由 KDE VDG 設計的 Breeze Keywords=Desktop;Workspace;Appearance;Look and Feel;Logout;Lock;Suspend;Shutdown;Hibernate; +Keywords[ast]=Escritoriu;Estaya de trabayu;Aspeutu;Estilu;Aspeutu y Estilu;Zarrar sesión;Zarru de sesión;Bloquiar;Bloquéu;Suspender;Suspensión;Apagar;Apagáu;Ivernar;Ivernación;Hibernar;Hibernación; Keywords[ca]=Escriptori;Espai de treball;Aparença;Aspecte i comportament;Sortida;Bloqueig;Suspensió;Aturada;Hibernació; -Keywords[ca@valencia]=Escriptori;Espai de treball;Aparença;Aspecte i comportament;Eixida;Bloqueig;Suspensió;Aturada;Hibernació; +Keywords[ca@valencia]=Escriptori;Espai de treball;Aparença;Aspecte i comportament;Eixida;Bloqueig;Suspensió;Parada;Hibernació; Keywords[da]=Skrivebord;Desktop;Arbejdsområde;Udseende;log ud;Lås;Suspendér;Nedlukning;dvale; -Keywords[de]=Desktop;Arbeitsfläche;Erscheinungsbild;Erscheinungsbild und Verhalten;Abmelden;Sperren;Standby;Ruhezustand;Tiefschlaf;Herunterfahren; +Keywords[de]=Desktop;Arbeitsfläche;Arbeitsbereich;Erscheinungsbild;Erscheinungsbild und Verhalten;Abmelden;Sperren;Standby;Ruhezustand;Tiefschlaf;Herunterfahren; Keywords[el]=Επιφάνεια εργασίας;Χώρος εργασίας;Εμφάνιση;Όψη και Αίσθηση;Αποσύνδεση;Κλείδωμα;Αναστολή;Τερματισμός;Νάρκη; Keywords[en_GB]=Desktop;Workspace;Appearance;Look and Feel;Logout;Lock;Suspend;Shutdown;Hibernate; Keywords[es]=Escritorio;Espacio de trabajo;Apariencia;Aspecto visual;Cerrar sesión;Bloquear;Suspender;Apagar;Hibernar; @@ -38,12 +44,12 @@ Keywords[fr]=bureau;espace de travail;apparence;déconnexion;verrouillage;suspension;arrêt;hibernation; Keywords[gl]=Escritorio;Espazo de traballo;Aparencia;Aparencia e Comportamento;Saír;Trancar;Bloquear;Suspender;Apagar;Hibernar; Keywords[hu]=Asztal;Munkaterület;Megjelenés;Kinézet;Kijelentkezés;Zárolás;Felfüggesztés;Leállítás;Hibernálás; -Keywords[id]=Desktop;Workspace;Penampilan;Look and Feel;Logout;Kunci;Suspensi;Mematikan;Hibernasi; +Keywords[id]=Desktop;Ruangkerja;Penampilan;Nuansa dan Suasana,Look and Feel;Logout;Kunci;Suspensi;Mematikan;Hibernasi; Keywords[is]=skjáborð;vinnusvæði;útlit;ásýnd og hegðun;Útskráning;Læsing;setja í bið;slökkva;svæfa; Keywords[it]=desktop;spazio di lavoro;aspetto;uscita;blocco;sospensione;spegnimento;ibernazione; Keywords[ja]=デスクトップ;ワークスペース;外観;外観と挙動;ログアウト;ロック;サスペンド;シャットダウン;休止状態; Keywords[ko]=Desktop;Workspace;Appearance;Look and Feel;Logout;Lock;Suspend;Shutdown;Hibernate;데스크톱;작업공간;모양;로그아웃;잠금;대기모드;종료;끄기;최대절전모드; -Keywords[lt]=Darbalaukis;Erdvė;Išvaizda;Išvaizda ir turinys;Atsijungti;užrakinti;sulaikyti;Išjungti;Hibernuoti; +Keywords[lt]=Darbalaukis;Darbo sritis;Erdvė;Erdve;Isvaizda;Isvaizda ir turinys;Išvaizda;Išvaizda ir turinys;Atsijungti;Užrakinti;Uzrakinti;Pristabdyti;Sulaikyti;Užmigdyti;Uzmigdyti;Isjungti;Išjungti;Hibernuoti; Keywords[nl]=Bureaublad;Werkruimte;Uiterlijk;Uiterlijk en gedrag;Afmelden;Vergrendelen;Onderbreken;Afsluiten;Slapen naar schijf; Keywords[nn]=skrivebord;arbeidsområde;arbeidsflate;ustjånad;åtferd;utlogging;logga ut;låsa;låsing;pausemodus;kvilemodus;dvalemodus;avslutting; Keywords[pa]=ਡੈਸਕਟਾਪ;ਵਰਕਸਪੇਸ;ਦਿੱਖ;ਲਾਗਆਉਟ;ਲਾਕ;ਸਸਪੈਂਡ;ਬੰਦ ਕਰੋ;ਹਾਈਬਰਨੇਟ; @@ -57,14 +63,15 @@ Keywords[sr@ijekavian]=Desktop;Workspace;Appearance;Look and Feel;Logout;Lock;Suspend;Shutdown;Hibernate;површ;радни простор;изглед;осећај;одјављивање;закључавање;суспендовање;гашење;хибернисање; Keywords[sr@ijekavianlatin]=Desktop;Workspace;Appearance;Look and Feel;Logout;Lock;Suspend;Shutdown;Hibernate;površ;radni prostor;izgled;osećaj;odjavljivanje;zaključavanje;suspendovanje;gašenje;hibernisanje; Keywords[sr@latin]=Desktop;Workspace;Appearance;Look and Feel;Logout;Lock;Suspend;Shutdown;Hibernate;površ;radni prostor;izgled;osećaj;odjavljivanje;zaključavanje;suspendovanje;gašenje;hibernisanje; -Keywords[sv]=Skrivbord;Arbetsrymd;Utseende;Utseende och känsla;Utloggning;Lås;Viloläge;Avstängning;Dvala; +Keywords[sv]=Skrivbord;Arbetsområde;Utseende;Utseende och känsla;Utloggning;Lås;Viloläge;Avstängning;Dvala; Keywords[tr]=Masaüstü;Çalışma Alanı;Görünüm;Görünüm ve Doku;Çık;Kilitle;Askıya Al;Kapat;Uyku; Keywords[uk]=Desktop;Workspace;Appearance;Look and Feel;Logout;Lock;Suspend;Shutdown;Hibernate;стільниця;робочий простір;воркспейс;вигляд;вигляд і поведінка;вихід;вийти;заблокувати;замкнути;призупинити;вимкнути;приспати; Keywords[x-test]=xxDesktopxx;xxWorkspacexx;xxAppearancexx;xxLook and Feelxx;xxLogoutxx;xxLockxx;xxSuspendxx;xxShutdownxx;xxHibernatexx; Keywords[zh_CN]=桌面;工作空间;外观;观感;注销;锁定;待机;关机;休眠; Keywords[zh_TW]=Desktop;Workspace;Appearance;Look and Feel;Logout;Lock;Suspend;Shutdown;Hibernate; Name=Breeze Name[ar]=نسيم +Name[ast]=Breeze Name[bs]=Breeze Name[ca]=Brisa Name[ca@valencia]=Brisa @@ -102,6 +109,7 @@ Name[sr@ijekavianlatin]=Povetarac Name[sr@latin]=Povetarac Name[sv]=Breeze +Name[tg]=Насим Name[tr]=Breeze Name[uk]=Breeze Name[x-test]=xxBreezexx @@ -117,5 +125,5 @@ X-KDE-PluginInfo-License=GPLv2+ X-KDE-PluginInfo-Name=org.kde.breeze.desktop X-KDE-PluginInfo-Version=2.0 -X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-Website=https://www.kde.org X-Plasma-MainScript=defaults diff --git a/menu/desktop/CMakeLists.txt b/menu/desktop/CMakeLists.txt --- a/menu/desktop/CMakeLists.txt +++ b/menu/desktop/CMakeLists.txt @@ -1,5 +1,5 @@ ########### install files ############### - + install( FILES kf5-development.directory kf5-development-translation.directory @@ -25,6 +25,7 @@ kf5-main.directory kf5-more.directory kf5-multimedia.directory + kf5-network.directory kf5-office.directory kf5-science.directory kf5-settingsmenu.directory diff --git a/menu/desktop/hidden.directory b/menu/desktop/hidden.directory --- a/menu/desktop/hidden.directory +++ b/menu/desktop/hidden.directory @@ -2,6 +2,7 @@ Type=Directory Name=Internal Services Name[ar]=الخدمات الداخلية +Name[ast]=Servicios internos Name[bs]=Unutrašnji servisi Name[ca]=Serveis interns Name[ca@valencia]=Serveis interns @@ -26,6 +27,7 @@ Name[ja]=内部サービス Name[ko]=내부 서비스 Name[lt]=Vidinės tarnybos +Name[lv]=Iekšējie servisi Name[nb]=Interne tjenester Name[nds]=Intern Deensten Name[nl]=Interne diensten @@ -42,6 +44,7 @@ Name[sr@ijekavianlatin]=Unutrašnji servisi Name[sr@latin]=Unutrašnji servisi Name[sv]=Interna tjänster +Name[tg]=Хизматҳои дохилӣ Name[tr]=Dahili Hizmetler Name[uk]=Внутрішні служби Name[x-test]=xxInternal Servicesxx diff --git a/menu/desktop/kf5-development-translation.directory b/menu/desktop/kf5-development-translation.directory --- a/menu/desktop/kf5-development-translation.directory +++ b/menu/desktop/kf5-development-translation.directory @@ -4,6 +4,7 @@ Name[af]=Vertaling Name[ar]=الترجمة Name[as]=অনুবাদ +Name[ast]=Traducción Name[be]=Пераклад Name[be@latin]=Pierakład Name[bg]=Превод @@ -78,7 +79,7 @@ Name[sv]=Översättning Name[ta]=மொழிபெயர்ப்பு Name[te]=అనువాదం -Name[tg]=Тарҷумонӣ +Name[tg]=Тарҷумаҳо Name[th]=แปลภาษา Name[tr]=Çeviri Name[ug]=تەرجىمىسى diff --git a/menu/desktop/kf5-development-webdevelopment.directory b/menu/desktop/kf5-development-webdevelopment.directory --- a/menu/desktop/kf5-development-webdevelopment.directory +++ b/menu/desktop/kf5-development-webdevelopment.directory @@ -4,6 +4,7 @@ Name[af]=Web Ontwikkeling Name[ar]=تطوير الوِب Name[as]=ৱেব বিকাশ +Name[ast]=Desendolcu web Name[be]=Web-распрацоўка Name[be@latin]=Sieciŭnaje raspracoŭvańnie Name[bg]=Уеб-разработка @@ -47,7 +48,7 @@ Name[kn]=ಜಾಲ ಅಭಿವೃದ್ಧಿ Name[ko]=웹 개발 Name[ku]=Pêşdebirina Torê -Name[lt]=Žiniatinklio programavimas +Name[lt]=Saityno programavimas Name[lv]=Tīmekļa izstrāde Name[mai]=वेब स्थल विकास Name[mk]=Веб-развој @@ -77,7 +78,7 @@ Name[sv]=Webbutveckling Name[ta]=இணைய உருவாக்கம் Name[te]=వెబ్ డెవలప్ మెంట్ -Name[tg]=Барномасозии Интернетӣ +Name[tg]=Барномарезии сомона Name[th]=พัฒนาเว็บ Name[tr]=Web Geliştirme Name[ug]=Web ئىجادىيىتى diff --git a/menu/desktop/kf5-development.directory b/menu/desktop/kf5-development.directory --- a/menu/desktop/kf5-development.directory +++ b/menu/desktop/kf5-development.directory @@ -4,6 +4,7 @@ Name[af]=Ontwikkeling Name[ar]=التطوير Name[as]=বিকাশ +Name[ast]=Desendolcu Name[be]=Распрацоўка Name[be@latin]=Raspracoŭvańnie Name[bg]=Разработка @@ -79,7 +80,7 @@ Name[sv]=Utveckling Name[ta]=உருவாக்கம் Name[te]=డెవలప్ మెంట్ -Name[tg]=Барномасозӣ +Name[tg]=Барномарезӣ Name[th]=พัฒนาโปรแกรม Name[tr]=Geliştirme Name[ug]=ئىجادىيەت diff --git a/menu/desktop/kf5-editors.directory b/menu/desktop/kf5-editors.directory --- a/menu/desktop/kf5-editors.directory +++ b/menu/desktop/kf5-editors.directory @@ -4,6 +4,7 @@ Name[af]=Redigeerders Name[ar]=المحرّرات Name[as]=সম্পাদক +Name[ast]=Editores Name[be]=Рэдактары Name[be@latin]=Redaktary Name[bg]=Редактори @@ -78,7 +79,7 @@ Name[sv]=Editorer Name[ta]=தொகுப்பாளர்கள் Name[te]=ఎడిటర్లు -Name[tg]=Таҳриргар +Name[tg]=Муҳаррирон Name[th]=เครื่องมือแก้ไขข้อความ Name[tr]=Düzenleyiciler Name[ug]=تەھرىرلىگۈچ diff --git a/menu/desktop/kf5-edu-languages.directory b/menu/desktop/kf5-edu-languages.directory --- a/menu/desktop/kf5-edu-languages.directory +++ b/menu/desktop/kf5-edu-languages.directory @@ -5,6 +5,7 @@ Name[af]=Tale Name[ar]=اللغات Name[as]=ভাষা +Name[ast]=Llingües Name[be]=Мовы Name[be@latin]=Movy Name[bg]=Езици diff --git a/menu/desktop/kf5-edu-mathematics.directory b/menu/desktop/kf5-edu-mathematics.directory --- a/menu/desktop/kf5-edu-mathematics.directory +++ b/menu/desktop/kf5-edu-mathematics.directory @@ -5,6 +5,7 @@ Name[af]=Wiskunde Name[ar]=الرياضيات Name[as]=গণিত +Name[ast]=Matemátiques Name[be]=Матэматыка Name[be@latin]=Matematyka Name[bg]=Математика diff --git a/menu/desktop/kf5-edu-miscellaneous.directory b/menu/desktop/kf5-edu-miscellaneous.directory --- a/menu/desktop/kf5-edu-miscellaneous.directory +++ b/menu/desktop/kf5-edu-miscellaneous.directory @@ -5,6 +5,7 @@ Name[af]=Allerlei Name[ar]=متنوّع Name[as]=বিবিধ +Name[ast]=Miscelánea Name[be]=Рознае Name[be@latin]=Roznaje Name[bg]=Разни @@ -37,7 +38,7 @@ Name[hsb]=Wšelake Name[hu]=Egyéb Name[ia]=Miscellanea -Name[id]=Lain-lain +Name[id]=Beraneka Name[is]=Ýmislegt Name[it]=Varie Name[ja]=その他 @@ -75,7 +76,7 @@ Name[sv]=Diverse Name[ta]=மற்றவை Name[te]=ఇతరత్రా -Name[tg]=Барномаҳои иловагӣ +Name[tg]=Гуногун Name[th]=เบ็ดเตล็ด Name[tr]=Diğer Name[ug]=باشقىلار diff --git a/menu/desktop/kf5-edu-science.directory b/menu/desktop/kf5-edu-science.directory --- a/menu/desktop/kf5-edu-science.directory +++ b/menu/desktop/kf5-edu-science.directory @@ -5,6 +5,7 @@ Name[af]=Wetenskap Name[ar]=العلوم Name[as]=বিজ্ঞান +Name[ast]=Ciencia Name[be]=Навука Name[be@latin]=Navuka Name[bg]=Наука @@ -76,7 +77,7 @@ Name[sv]=Vetenskap Name[ta]=அறிவியல் Name[te]=సాంకేతికశాస్త్రం -Name[tg]=Илм +Name[tg]=Назария Name[th]=วิทยาศาสตร์ Name[tr]=Bilim Name[ug]=ئىلىم diff --git a/menu/desktop/kf5-edu-tools.directory b/menu/desktop/kf5-edu-tools.directory --- a/menu/desktop/kf5-edu-tools.directory +++ b/menu/desktop/kf5-edu-tools.directory @@ -5,6 +5,7 @@ Name[af]=Onderrighulpbronne Name[ar]=أدوات التعليم Name[as]=শৈক্ষিক সৰঞ্জাম +Name[ast]=Ferramientes de deprendizaxe Name[be]=Праграмы для навучання Name[be@latin]=Navučalnyja prahramy Name[bg]=Учебни пособия @@ -37,7 +38,7 @@ Name[hsb]=Wuwučowanje Name[hu]=Oktátási kellékek Name[ia]=Instrumentos per inseniar -Name[id]=Alat Pengajaran +Name[id]=Peralatan Pengajaran Name[is]=Kennsluáhöld Name[it]=Didattica Name[ja]=先生のためのツール @@ -74,7 +75,6 @@ Name[sv]=Utlärningsverktyg Name[ta]=பயிற்று கருவிகள் Name[te]=భోధనా సాధనములు -Name[tg]=Барномаҳои таълимӣ Name[th]=เครื่องมือช่วยการสอน Name[tr]=Öğrenim Araçları Name[ug]=ئوقۇتۇش قوراللىرى diff --git a/menu/desktop/kf5-education.directory b/menu/desktop/kf5-education.directory --- a/menu/desktop/kf5-education.directory +++ b/menu/desktop/kf5-education.directory @@ -2,6 +2,7 @@ Type=Directory Name=Education Name[ar]=التعليم +Name[ast]=Educación Name[bs]=Edukacija Name[ca]=Educació Name[ca@valencia]=Educació @@ -27,6 +28,7 @@ Name[ja]=教育 Name[ko]=교육 Name[lt]=Švietimas +Name[lv]=Izglītība Name[nb]=Utdanning Name[nds]=Lehren Name[nl]=Onderwijs @@ -44,6 +46,7 @@ Name[sr@ijekavianlatin]=Obrazovanje Name[sr@latin]=Obrazovanje Name[sv]=Utbildning +Name[tg]=Илму маърифат Name[tr]=Eğitim Name[uk]=Освіта Name[x-test]=xxEducationxx diff --git a/menu/desktop/kf5-games-arcade.directory b/menu/desktop/kf5-games-arcade.directory --- a/menu/desktop/kf5-games-arcade.directory +++ b/menu/desktop/kf5-games-arcade.directory @@ -4,6 +4,7 @@ Name[af]=Arkade Name[ar]=الممرّات Name[as]=আৰ্কেড +Name[ast]=Arcade Name[be]=Аркады Name[be@latin]=Arkady Name[bg]=Аркадни @@ -22,7 +23,7 @@ Name[eo]=Arkado Name[es]=Arcade Name[et]=Võitlusmängud -Name[eu]=Arkade-jokoak +Name[eu]=Arkupe Name[fa]=گذرگاه تاقدار Name[fi]=Pelihallipelit Name[fr]=Jeux d'arcade @@ -76,7 +77,6 @@ Name[sv]=Arkadspel Name[ta]=ஆர்கேட் Name[te]=ఆర్కేడ్ -Name[tg]=Силсилабанд Name[th]=เกมอาเขต Name[tr]=Oyun Makinesi Name[ug]=Arcade @@ -87,7 +87,7 @@ Name[wa]=Årcåde Name[xh]=Umqolo wesakhiwo esiphatha ubunzima bento Name[x-test]=xxArcadexx -Name[zh_CN]=街机 +Name[zh_CN]=街机游戏 Name[zh_TW]=大型遊戲機遊戲 Icon=applications-games-arcade X-KDE-SuppressGenericNames=Game,Arcade Game diff --git a/menu/desktop/kf5-games-board.directory b/menu/desktop/kf5-games-board.directory --- a/menu/desktop/kf5-games-board.directory +++ b/menu/desktop/kf5-games-board.directory @@ -4,6 +4,7 @@ Name[af]=Bord Speletjies Name[ar]=ألعاب الألواح Name[as]=বোৰ্ড খেলা +Name[ast]=Xuegos de mesa Name[be]=Настольныя гульні Name[be@latin]=Nastolnyja hulni Name[bg]=Игри на дъска @@ -77,7 +78,6 @@ Name[sv]=Brädspel Name[ta]=மேசை விளையாட்டுகள் Name[te]=పలక ఆటలు -Name[tg]=Бозиҳои рӯимизӣ Name[th]=เกมกระดาน Name[tr]=Masaüstü Oyunları Name[ug]=تاختاي ئويۇنلىرى diff --git a/menu/desktop/kf5-games-card.directory b/menu/desktop/kf5-games-card.directory --- a/menu/desktop/kf5-games-card.directory +++ b/menu/desktop/kf5-games-card.directory @@ -4,6 +4,7 @@ Name[af]=Kaart Speletjies Name[ar]=ألعاب الورق Name[as]=তাছ খেলা +Name[ast]=Xuegos de cartes Name[be]=Картавыя гульні Name[be@latin]=Hulni ŭ karty Name[bg]=Игри с карти @@ -78,7 +79,6 @@ Name[sv]=Kortspel Name[ta]=சீட்டு விளையாட்டுகள் Name[te]=పేక ఆటలు -Name[tg]=Қартабозӣ Name[th]=เกมแนวไพ่ Name[tr]=Kart Oyunları Name[ug]=قارتا ئويۇنلىرى diff --git a/menu/desktop/kf5-games-kids.directory b/menu/desktop/kf5-games-kids.directory --- a/menu/desktop/kf5-games-kids.directory +++ b/menu/desktop/kf5-games-kids.directory @@ -4,6 +4,7 @@ Name[af]=Kinder Speletjies Name[ar]=ألعاب الأطفال Name[as]=সৰু ল'ৰা ছোৱালিৰ খেলা +Name[ast]=Xuegos pa guaḥes Name[be]=Дзіцячыя гульні Name[be@latin]=Dziciačyja hulni Name[bg]=Игри за деца @@ -77,7 +78,6 @@ Name[sv]=Spel för barn Name[ta]=குழந்தைகளுக்கான விளையாட்டுகள் Name[te]=పిల్లల ఆటలు -Name[tg]=Бозиҳои бачагона Name[th]=เกมสำหรับเด็ก Name[tr]=Çocuklar için Oyunlar Name[ug]=بالىلار ئۈچۈن ئويۇنلار diff --git a/menu/desktop/kf5-games-logic.directory b/menu/desktop/kf5-games-logic.directory --- a/menu/desktop/kf5-games-logic.directory +++ b/menu/desktop/kf5-games-logic.directory @@ -3,6 +3,7 @@ Name=Logic Games Name[ar]=ألعاب المنطق Name[as]=লজিক খেলা +Name[ast]=Xuegos de llóxica Name[be@latin]=Lahičnyja hulni Name[bg]=Логически игри Name[bn]=যুক্তির খেলা @@ -68,7 +69,6 @@ Name[sr@latin]=Logičke igre Name[sv]=Logiska spel Name[ta]=லாஜிக் விளையாட்டு -Name[tg]=Бозиҳои мантиқӣ Name[th]=เกมแนวตรรกะ Name[tr]=Mantık Oyunları Name[ug]=لوگىكىلىق ئويۇن diff --git a/menu/desktop/kf5-games-roguelikes.directory b/menu/desktop/kf5-games-roguelikes.directory --- a/menu/desktop/kf5-games-roguelikes.directory +++ b/menu/desktop/kf5-games-roguelikes.directory @@ -10,7 +10,7 @@ Name[br]=Ur c'hoari a seurt gant Rogue Name[bs]=Igre nalik na Roug Name[ca]=Jocs com el Rogue -Name[ca@valencia]=Jocs com el Rogue +Name[ca@valencia]=Jocs com Rogue Name[cs]=Hry podobné Rogue Name[csb]=Gré z familëji Rogue Name[cy]=Gemau sy'n debyg i Rogue @@ -75,7 +75,6 @@ Name[sv]=Spel som liknar Rogue Name[ta]=முரட்டுதனமான விளையாட்டுகள் Name[te]=రోగ్ లాంటి ఆటలు -Name[tg]=Ҳуққабозӣ Name[th]=เกมคล้าย Rogue Name[tr]=Rogue benzeri Oyunlar Name[ug]=بىمەنە چاقچاق ئويۇنلار @@ -86,5 +85,5 @@ Name[wa]=Djeus ki rshonnèt-st a « rogue » Name[xh]=Imidlalo efana ne Rogue Name[x-test]=xxRogue-like Gamesxx -Name[zh_CN]=恶作剧游戏 +Name[zh_CN]=地牢游戏 Name[zh_TW]=冒險類遊戲 diff --git a/menu/desktop/kf5-games-strategy.directory b/menu/desktop/kf5-games-strategy.directory --- a/menu/desktop/kf5-games-strategy.directory +++ b/menu/desktop/kf5-games-strategy.directory @@ -4,6 +4,7 @@ Name[af]=Strategie & Taktiek Name[ar]=التكتيكات والاستراتيجيات Name[as]=Tactics & Strategy +Name[ast]=Táutiques y estratexa Name[be]=Тактыка і стратэгія Name[be@latin]=Taktyka j stratehija Name[bg]=Стратегически игри @@ -76,7 +77,6 @@ Name[sv]=Taktik och strategi Name[ta]=உத்திகளும் தந்திரங்களும் Name[te]=యుక్తి & తంత్రాలు -Name[tg]=Тадбирӣ ва стратегӣ Name[th]=เกมกลยุทธ์และวางแผน Name[tr]=Taktik ve Strateji Name[ug]=تاكتىكا ۋە ئىستراتېگىيە diff --git a/menu/desktop/kf5-games.directory b/menu/desktop/kf5-games.directory --- a/menu/desktop/kf5-games.directory +++ b/menu/desktop/kf5-games.directory @@ -4,6 +4,7 @@ Name[af]=Speletjies Name[ar]=الألعاب Name[as]=খেলা +Name[ast]=Xuegos Name[be]=Гульні Name[be@latin]=Hulni Name[bg]=Игри diff --git a/menu/desktop/kf5-graphics.directory b/menu/desktop/kf5-graphics.directory --- a/menu/desktop/kf5-graphics.directory +++ b/menu/desktop/kf5-graphics.directory @@ -4,6 +4,7 @@ Name[af]=Grafieka Name[ar]=الرسوميات Name[as]=চিত্ৰাঙ্কন +Name[ast]=Gráficos Name[be]=Графіка Name[be@latin]=Hrafika Name[bg]=Графика diff --git a/menu/desktop/kf5-internet-terminal.directory b/menu/desktop/kf5-internet-terminal.directory --- a/menu/desktop/kf5-internet-terminal.directory +++ b/menu/desktop/kf5-internet-terminal.directory @@ -15,7 +15,7 @@ Name[eu]=Terminaleko aplikazioak Name[fi]=Päätesovellukset Name[fr]=Applications pour terminal -Name[gl]=Aplicativos de terminal +Name[gl]=Aplicacións de terminal Name[he]=ישומי טרמינל Name[hr]=Terminalske aplikacije Name[hu]=Parancsértelmezők @@ -25,7 +25,8 @@ Name[it]=Applicazioni di terminale Name[ja]=ターミナルアプリケーション Name[ko]=터미널 프로그램 -Name[lt]=Terminalinės programos +Name[lt]=Terminalo programos +Name[lv]=Komandrindas programmas Name[nb]=Terminalprogrammer Name[nds]=Terminalprogrammen Name[nl]=Terminal-toepassingen @@ -45,7 +46,7 @@ Name[tr]=Uçbirim Uygulamaları Name[uk]=Термінальні програми Name[x-test]=xxTerminal Applicationsxx -Name[zh_CN]=终端应用程序 +Name[zh_CN]=终端应用 Name[zh_TW]=終端機程式 Icon=utilities-terminal Version=1.0 diff --git a/menu/desktop/kf5-internet.directory b/menu/desktop/kf5-internet.directory --- a/menu/desktop/kf5-internet.directory +++ b/menu/desktop/kf5-internet.directory @@ -4,6 +4,7 @@ Name[af]=Internet Name[ar]=الإنترنت Name[as]=ইন্টাৰ্নে'ট +Name[ast]=Internet Name[be]=Інтэрнэт Name[be@latin]=Internet Name[bg]=Интернет diff --git a/menu/desktop/kf5-main.directory b/menu/desktop/kf5-main.directory --- a/menu/desktop/kf5-main.directory +++ b/menu/desktop/kf5-main.directory @@ -5,15 +5,16 @@ Name[af]=KDE Kieslys Name[ar]=قائمة كدي Name[as]=KDE তালিকা +Name[ast]=Menú de KDE Name[be]=Меню KDE Name[be@latin]=Menu KDE Name[bg]=Главно меню Name[bn]=কে.ডি.ই. মেনু Name[bn_IN]=KDE মেনু Name[br]=Meuziad KDE Name[bs]=KDE‑ov meni Name[ca]=Menú del KDE -Name[ca@valencia]=Menú del KDE +Name[ca@valencia]=Menú de KDE Name[cs]=Nabídka KDE Name[csb]=KDE menu Name[cy]=Dewislen KDE @@ -70,16 +71,16 @@ Name[ro]=Meniu KDE Name[ru]=Меню KDE Name[si]=KDE මෙනුව -Name[sk]=Menu KDE +Name[sk]=Ponuka KDE Name[sl]=Meni KDE Name[sr]=КДЕ мени Name[sr@ijekavian]=КДЕ мени Name[sr@ijekavianlatin]=KDE meni Name[sr@latin]=KDE meni Name[sv]=KDE-meny Name[ta]=கேடியி பட்டி Name[te]=కెడిఈ పట్టీ -Name[tg]=Менюи KDE +Name[tg]=Феҳристи KDE Name[th]=เมนู KDE Name[tr]=KDE Menüsü Name[ug]=KDE تىزىملىك diff --git a/menu/desktop/kf5-more.directory b/menu/desktop/kf5-more.directory --- a/menu/desktop/kf5-more.directory +++ b/menu/desktop/kf5-more.directory @@ -3,6 +3,7 @@ Icon=applications-other Name=More Applications Name[ar]=تطبيقات أكثر +Name[ast]=Más apliaciones Name[bs]=Više aplikacija Name[ca]=Més aplicacions Name[ca@valencia]=Més aplicacions @@ -16,7 +17,7 @@ Name[eu]=Aplikazio gehiago Name[fi]=Lisää sovelluksia Name[fr]=Plus d'applications -Name[gl]=Máis aplicativos +Name[gl]=Máis aplicacións Name[he]=עוד ישומים Name[hr]=Više aplikacija Name[hu]=További alkalmazások @@ -27,6 +28,7 @@ Name[ja]=その他のアプリケーション Name[ko]=더 많은 프로그램 Name[lt]=Daugiau programų +Name[lv]=Vairāk programmu Name[nb]=Flere programmer Name[nds]=Mehr Programmen Name[nl]=Meer programma's @@ -43,8 +45,9 @@ Name[sr@ijekavianlatin]=Još programa Name[sr@latin]=Još programa Name[sv]=Fler program +Name[tg]=Барномаҳои бештар Name[tr]=Daha Fazla Uygulama Name[uk]=Інші програми Name[x-test]=xxMore Applicationsxx -Name[zh_CN]=更多应用程序 +Name[zh_CN]=更多应用 Name[zh_TW]=更多應用程式 diff --git a/menu/desktop/kf5-multimedia.directory b/menu/desktop/kf5-multimedia.directory --- a/menu/desktop/kf5-multimedia.directory +++ b/menu/desktop/kf5-multimedia.directory @@ -4,6 +4,7 @@ Name[af]=Multimedia Name[ar]=الوسائط المتعدّدة Name[as]=মাল্টিমিডিয়া +Name[ast]=Multimedia Name[be]=Мультымедыя Name[be@latin]=Multymedyja Name[bg]=Мултимедия @@ -90,7 +91,7 @@ Name[wa]=Multimedia Name[xh]=Iindlela ezininzi zokwenza Name[x-test]=xxMultimediaxx -Name[zh_CN]=多媒体 +Name[zh_CN]=影音 Name[zh_TW]=多媒體 Icon=applications-multimedia diff --git a/menu/desktop/kf5-network.directory b/menu/desktop/kf5-network.directory new file mode 100644 --- /dev/null +++ b/menu/desktop/kf5-network.directory @@ -0,0 +1,31 @@ +[Desktop Entry] +Type=Directory +Name=Network +Name[ast]=Rede +Name[ca]=Xarxa +Name[cs]=Síť +Name[de]=Netzwerk +Name[en_GB]=Network +Name[es]=Red +Name[et]=Võrk +Name[eu]=Sarea +Name[fr]=Réseau +Name[gl]=Rede +Name[hu]=Hálózat +Name[id]=Jaringan +Name[it]=Rete +Name[lt]=Tinklas +Name[lv]=Tīkls +Name[nl]=Netwerk +Name[nn]=Nettverk +Name[pl]=Sieć +Name[pt]=Rede +Name[pt_BR]=Rede +Name[ru]=Сеть +Name[sk]=Sieť +Name[sv]=Nätverk +Name[uk]=Мережа +Name[x-test]=xxNetworkxx +Name[zh_CN]=网络 +Name[zh_TW]=網路 +Icon=applications-network diff --git a/menu/desktop/kf5-office.directory b/menu/desktop/kf5-office.directory --- a/menu/desktop/kf5-office.directory +++ b/menu/desktop/kf5-office.directory @@ -4,6 +4,7 @@ Name[af]=Kantoor Name[ar]=المكتب Name[as]=কাৰ্যালয় +Name[ast]=Oficina Name[be]=Офіс Name[be@latin]=Ofis Name[bg]=Офис diff --git a/menu/desktop/kf5-science.directory b/menu/desktop/kf5-science.directory --- a/menu/desktop/kf5-science.directory +++ b/menu/desktop/kf5-science.directory @@ -5,6 +5,7 @@ Name[af]=Wetenskap & Wiskunde Name[ar]=العلوم والرياضيات Name[as]=বিজ্ঞান & গণিত +Name[ast]=Ciencia y matemátiques Name[be]=Навука і матэматыка Name[be@latin]=Navuka j matematyka Name[bg]=Наука и математика @@ -78,7 +79,7 @@ Name[sv]=Vetenskap och matematik Name[ta]=அறிவியல் & கணிதம் Name[te]=విజ్ఞానం & గణితం -Name[tg]=Илм ва риёзиёт +Name[tg]=Назария ва риёзиёт Name[th]=วิทยาศาสตร์และคณิตศาสตร์ Name[tr]=Bilim ve Matematik Name[ug]=پەن ۋە ماتېماتىكا diff --git a/menu/desktop/kf5-settingsmenu.directory b/menu/desktop/kf5-settingsmenu.directory --- a/menu/desktop/kf5-settingsmenu.directory +++ b/menu/desktop/kf5-settingsmenu.directory @@ -4,6 +4,7 @@ Name[af]=Instellings Name[ar]=الإعدادات Name[as]=বৈশিষ্ট্য +Name[ast]=Axustes Name[be]=Настаўленні Name[be@latin]=Nałady Name[bg]=Настройки @@ -38,7 +39,7 @@ Name[hsb]=Nastajenja Name[hu]=Beállítások Name[ia]=Preferentias -Name[id]=Setelan +Name[id]=Pengaturan Name[is]=Stillingar Name[it]=Impostazioni Name[ja]=設定 diff --git a/menu/desktop/kf5-system-terminal.directory b/menu/desktop/kf5-system-terminal.directory --- a/menu/desktop/kf5-system-terminal.directory +++ b/menu/desktop/kf5-system-terminal.directory @@ -15,7 +15,7 @@ Name[eu]=Terminaleko aplikazioak Name[fi]=Päätesovellukset Name[fr]=Applications pour terminal -Name[gl]=Aplicativos de terminal +Name[gl]=Aplicacións de terminal Name[he]=ישומי טרמינל Name[hr]=Terminalske aplikacije Name[hu]=Parancsértelmezők @@ -25,7 +25,8 @@ Name[it]=Applicazioni di terminale Name[ja]=ターミナルアプリケーション Name[ko]=터미널 프로그램 -Name[lt]=Terminalinės programos +Name[lt]=Terminalo programos +Name[lv]=Komandrindas programmas Name[nb]=Terminalprogrammer Name[nds]=Terminalprogrammen Name[nl]=Terminal-toepassingen @@ -45,7 +46,7 @@ Name[tr]=Uçbirim Uygulamaları Name[uk]=Термінальні програми Name[x-test]=xxTerminal Applicationsxx -Name[zh_CN]=终端应用程序 +Name[zh_CN]=终端应用 Name[zh_TW]=終端機程式 Icon=utilities-terminal Version=1.0 diff --git a/menu/desktop/kf5-system.directory b/menu/desktop/kf5-system.directory --- a/menu/desktop/kf5-system.directory +++ b/menu/desktop/kf5-system.directory @@ -3,6 +3,7 @@ Name=System Name[ar]=النظام Name[as]=ব্যৱস্থাপ্ৰণালী +Name[ast]=Sistema Name[bs]=Sistem Name[ca]=Sistema Name[ca@valencia]=Sistema @@ -28,6 +29,7 @@ Name[ja]=システム Name[ko]=시스템 Name[lt]=Sistema +Name[lv]=Sistēma Name[nb]=System Name[nds]=Systeem Name[nl]=Systeem @@ -45,6 +47,7 @@ Name[sr@ijekavianlatin]=Sistem Name[sr@latin]=Sistem Name[sv]=System +Name[tg]=Низом Name[tr]=Sistem Name[uk]=Система Name[x-test]=xxSystemxx diff --git a/menu/desktop/kf5-toys.directory b/menu/desktop/kf5-toys.directory --- a/menu/desktop/kf5-toys.directory +++ b/menu/desktop/kf5-toys.directory @@ -3,15 +3,16 @@ Name=Toys Name[af]=Speelgoed Name[as]=খেলনা +Name[ast]=Xuguetes Name[be]=Цацкі Name[be@latin]=Cacki Name[bg]=Играчки Name[bn]=খেলনা Name[bn_IN]=খেলনা Name[br]=C'hoarielloù Name[bs]=Igračke Name[ca]=Joguines -Name[ca@valencia]=Joguines +Name[ca@valencia]=Joguets Name[cs]=Hračky Name[csb]=Zabôwczi Name[cy]=Tegannau diff --git a/menu/desktop/kf5-unknown.directory b/menu/desktop/kf5-unknown.directory --- a/menu/desktop/kf5-unknown.directory +++ b/menu/desktop/kf5-unknown.directory @@ -4,6 +4,7 @@ Name[af]=Verlore & Gevind Name[ar]=ضاع ثمّ وُجِد Name[as]=হেৰুৱা & পোৱা +Name[ast]=Oxetos perdíos Name[be]=Згубленыя і знойдзеныя Name[be@latin]=Biez katehoryi Name[bg]=Разни @@ -23,7 +24,7 @@ Name[eo]=Perditaj kaj trovitaj Name[es]=Objetos perdidos Name[et]=Tundmatud -Name[eu]=Galdua & Aurkitua +Name[eu]=Galdutako gauzak Name[fa]=گم‌شده و یافته‌شده Name[fi]=Sekalaiset Name[fr]=Objets trouvés @@ -77,7 +78,6 @@ Name[sv]=Hittegods Name[ta]=இழந்த & கிடைத்த Name[te]=పోయినవి & దొరికినవి -Name[tg]=Феҳристи бозёфтҳо Name[th]=จัดหมวดหมู่ไม่ได้ Name[tr]=Bulunanlar Name[ug]=مۆكىمۆكىلەڭ diff --git a/menu/desktop/kf5-utilities-accessibility.directory b/menu/desktop/kf5-utilities-accessibility.directory --- a/menu/desktop/kf5-utilities-accessibility.directory +++ b/menu/desktop/kf5-utilities-accessibility.directory @@ -4,6 +4,7 @@ Name[af]=Toeganklikheid Name[ar]=الإتاحة Name[as]=অভিগম্যতা +Name[ast]=Accesibilidá Name[be]=Даступнасць Name[be@latin]=Dastupnaść Name[bg]=Равностоен достъп @@ -79,7 +80,7 @@ Name[sv]=Handikappstöd Name[ta]=அணுகல் Name[te]=అందుబాటు -Name[tg]=Барномаҳои дастёрӣ +Name[tg]=Қобилияти дастрасӣ Name[th]=ช่วยการใช้งาน Name[tr]=Erişilebilirlik Name[ug]=ياردەم ئىقتىدارى @@ -97,6 +98,7 @@ Comment[af]=Toeganklikheid Comment[ar]=الإتاحة Comment[as]=অভিগম্যতা +Comment[ast]=Accesibilidá Comment[be]=Даступнасць Comment[be@latin]=Dastupnaść Comment[bg]=Равностоен достъп @@ -171,7 +173,7 @@ Comment[sv]=Handikappstöd Comment[ta]=அணுகுத்தன்மை Comment[te]=అందుబాటు -Comment[tg]=Барномаҳои дастёрӣ +Comment[tg]=Қобилияти дастрасӣ Comment[th]=ช่วยการใช้งานให้ง่ายขึ้น Comment[tr]=Erişilebilirlik Comment[ug]=ياردەم ئىقتىدارى diff --git a/menu/desktop/kf5-utilities-desktop.directory b/menu/desktop/kf5-utilities-desktop.directory --- a/menu/desktop/kf5-utilities-desktop.directory +++ b/menu/desktop/kf5-utilities-desktop.directory @@ -2,6 +2,7 @@ Type=Directory Name=Desktop Name[ar]=سطح المكتب +Name[ast]=Escritoriu Name[bs]=Radna površina Name[ca]=Escriptori Name[ca@valencia]=Escriptori @@ -26,6 +27,7 @@ Name[ja]=デスクトップ Name[ko]=데스크톱 Name[lt]=Darbalaukis +Name[lv]=Darbvirsma Name[nb]=Skrivebord Name[nds]=Schriefdisch Name[nl]=Bureaublad @@ -43,14 +45,16 @@ Name[sr@ijekavianlatin]=Površ Name[sr@latin]=Površ Name[sv]=Skrivbord +Name[tg]=Мизи корӣ Name[tr]=Masaüstü Name[uk]=Стільниця Name[x-test]=xxDesktopxx Name[zh_CN]=桌面 Name[zh_TW]=桌面 Icon=user-desktop Comment=Desktop Comment[ar]=سطح المكتب +Comment[ast]=Escritoriu Comment[bs]=Radna površina Comment[ca]=Escriptori Comment[ca@valencia]=Escriptori @@ -75,6 +79,7 @@ Comment[ja]=デスクトップ Comment[ko]=데스크톱 Comment[lt]=Darbalaukis +Comment[lv]=Darbvirsma Comment[nb]=Skrivebord Comment[nds]=Schriefdisch Comment[nl]=Bureaublad @@ -91,6 +96,7 @@ Comment[sr@ijekavianlatin]=Površ Comment[sr@latin]=Površ Comment[sv]=Skrivbord +Comment[tg]=Мизи корӣ Comment[tr]=Masaüstü Comment[uk]=Стільниця Comment[x-test]=xxDesktopxx diff --git a/menu/desktop/kf5-utilities-file.directory b/menu/desktop/kf5-utilities-file.directory --- a/menu/desktop/kf5-utilities-file.directory +++ b/menu/desktop/kf5-utilities-file.directory @@ -3,6 +3,7 @@ Name=File Name[ar]=ملف Name[as]=নথিপত্ৰ +Name[ast]=Ficheru Name[bs]=Datoteka Name[ca]=Fitxer Name[ca@valencia]=Fitxer @@ -28,6 +29,7 @@ Name[ja]=ファイル Name[ko]=파일 Name[lt]=Failas +Name[lv]=Datne Name[nb]=Fil Name[nds]=Datei Name[nl]=Bestand @@ -45,6 +47,7 @@ Name[sr@ijekavianlatin]=Fajl Name[sr@latin]=Fajl Name[sv]=Arkiv +Name[tg]=Файл Name[tr]=Dosya Name[uk]=Файл Name[x-test]=xxFilexx @@ -54,6 +57,7 @@ Comment=File Comment[ar]=ملف Comment[as]=নথিপত্ৰ +Comment[ast]=Ficheru Comment[bs]=Datoteka Comment[ca]=Fitxer Comment[ca@valencia]=Fitxer @@ -79,6 +83,7 @@ Comment[ja]=ファイル Comment[ko]=파일 Comment[lt]=Failas +Comment[lv]=Datne Comment[nb]=Fil Comment[nds]=Datei Comment[nl]=Bestand @@ -96,6 +101,7 @@ Comment[sr@ijekavianlatin]=Fajl Comment[sr@latin]=Fajl Comment[sv]=Arkiv +Comment[tg]=Файл Comment[tr]=Dosya Comment[uk]=Файл Comment[x-test]=xxFilexx diff --git a/menu/desktop/kf5-utilities-peripherals.directory b/menu/desktop/kf5-utilities-peripherals.directory --- a/menu/desktop/kf5-utilities-peripherals.directory +++ b/menu/desktop/kf5-utilities-peripherals.directory @@ -4,6 +4,7 @@ Name[af]=Randapperatuur Name[ar]=الوحدات الملحقة Name[as]=যন্ত্ৰ +Name[ast]=Periféricos Name[be]=Перыферыя Name[be@latin]=Vonkavaja aparatura Name[bg]=Периферия @@ -47,7 +48,7 @@ Name[kn]=ಪೂರಕಯಂತ್ರಾಂಶಗಳು (ಪೆರಿಫೆರಲ್) Name[ko]=주변 장치 Name[ku]=Qalik -Name[lt]=Periferija +Name[lt]=Išoriniai įtaisai Name[lv]=Perifērija Name[mai]=सहायक पुरजा Name[mk]=Периферни уреди @@ -76,7 +77,6 @@ Name[sv]=Kringutrustning Name[ta]=கருவிகள் Name[te]=పరికరాలు -Name[tg]=Дастгоҳҳои компютерӣ Name[th]=อุปกรณ์ต่อพ่วง Name[tr]=Çevre Birimleri Name[ug]=قوشۇمچە ئۈسكۈنىلەر @@ -94,6 +94,7 @@ Comment[af]=Randapparatuur Comment[ar]=الوحدات الملحقة Comment[as]=যন্ত্ৰ +Comment[ast]=Periféricos Comment[be]=Перыферыя Comment[be@latin]=Vonkavaja aparatura Comment[bg]=Периферия @@ -136,7 +137,7 @@ Comment[kn]=ಪೂರಕಯಂತ್ರಾಂಶಗಳು (ಪೆರಿಫೆರಲ್) Comment[ko]=주변 장치 Comment[ku]=Qalik -Comment[lt]=Periferija +Comment[lt]=Išoriniai įtaisai Comment[lv]=Perifērijas ierīces Comment[mai]=सहायक पुरजा Comment[mk]=Периферни уреди @@ -165,7 +166,6 @@ Comment[sv]=Kringutrustning Comment[ta]=கருவிகள் Comment[te]=పరికరాలు -Comment[tg]=Дастгоҳҳои компютерӣ Comment[th]=อุปกรณ์ต่อพ่วง Comment[tr]=Çevre Birimleri Comment[ug]=قوشۇمچە ئۈسكۈنىلەر diff --git a/menu/desktop/kf5-utilities-pim.directory b/menu/desktop/kf5-utilities-pim.directory --- a/menu/desktop/kf5-utilities-pim.directory +++ b/menu/desktop/kf5-utilities-pim.directory @@ -3,6 +3,7 @@ Name=PIM Name[af]=PIM Name[as]=PIM +Name[ast]=XIP Name[be]=PIM Name[be@latin]=Ułasny arhanizatar Name[bg]=PIM @@ -75,7 +76,6 @@ Name[sv]=Personlig information Name[ta]=PIM Name[te]=పిఐఎం -Name[tg]=Мудири маълумот Name[th]=จัดการข้อมูลส่วนตัว (PIM) Name[tr]=Kişisel Bilgi Yönetimi Name[ug]=PIM @@ -89,6 +89,7 @@ Comment=PIM Comment[af]=Persoonlike Informasie Bestuurder Comment[as]=PIM +Comment[ast]=XIP Comment[be]=PIM Comment[be@latin]=Ułasny arhanizatar Comment[bg]=PIM @@ -161,7 +162,6 @@ Comment[sv]=Personlig information Comment[ta]=PIM Comment[te]=పిఐఎం -Comment[tg]=Мудири маълумот Comment[th]=จัดการข้อมูลส่วนตัว (PIM) Comment[tr]=Kişisel Bilgi Yönetimi Comment[ug]=PIM diff --git a/menu/desktop/kf5-utilities-xutils.directory b/menu/desktop/kf5-utilities-xutils.directory --- a/menu/desktop/kf5-utilities-xutils.directory +++ b/menu/desktop/kf5-utilities-xutils.directory @@ -3,6 +3,7 @@ Icon=x Name=X-Utilities Name[ar]=أدوات X +Name[ast]=Utilidaes de X Name[bs]=X-usluge Name[ca]=Utilitats de X Name[ca@valencia]=Utilitats de X @@ -26,7 +27,7 @@ Name[it]=Accessori di X Name[ja]=X ユーティリティ Name[ko]=X 유틸리티 -Name[lt]=X pagalbininkai +Name[lt]=X paslaugų programos Name[nb]=X-verktøy Name[nds]=X-Warktüüch Name[nl]=Grafische hulpprogramma's @@ -52,6 +53,7 @@ Comment[af]=X Window Nutsprogramme Comment[ar]=أدوات نافذة X Comment[as]=X Window Utilities +Comment[ast]=Utilidaes pa ventanes de X Comment[be]=Службовыя праграмы X Window Comment[be@latin]=Pryładździe systemy „X Window” Comment[bg]=Помощни програми за сървъра X @@ -96,7 +98,7 @@ Comment[kn]=ಕ್ಸ ಕಿಟಕಿ ಸೌಲಭ್ಯಗಳು Comment[ko]=X Window 유틸리티 Comment[ku]=Fêdeyên X-Windowê -Comment[lt]=X Window pagalbininkai +Comment[lt]=X Window paslaugų programos Comment[lv]=X logu utilītas Comment[mai]=एक्स विंडो यूटिलिटीज Comment[mk]=Алатки за X Window @@ -125,7 +127,6 @@ Comment[sv]=X-window-verktyg Comment[ta]=X-சாளர கருவிகள் Comment[te]=ఎక్స్ విండొ సౌలభ్యాలు -Comment[tg]=Барномаҳои системаи X Window Comment[th]=โปรแกรมอรรถประโยชน์ต่าง ๆ ของระบบ X Window Comment[tr]=X Yardımcı Programları Comment[ug]=X كۆزنەك قوراللىرى diff --git a/menu/desktop/kf5-utilities.directory b/menu/desktop/kf5-utilities.directory --- a/menu/desktop/kf5-utilities.directory +++ b/menu/desktop/kf5-utilities.directory @@ -2,6 +2,7 @@ Type=Directory Name=Utilities Name[ar]=الأدوات +Name[ast]=Utilidaes Name[bs]=Usluge Name[ca]=Utilitats Name[ca@valencia]=Utilitats @@ -26,7 +27,8 @@ Name[it]=Accessori Name[ja]=ユーティリティ Name[ko]=유틸리티 -Name[lt]=Pagalbininkai +Name[lt]=Paslaugų programos +Name[lv]=Utilītprogrammas Name[nb]=Verktøy Name[nds]=Warktüüch Name[nl]=Hulpmiddelen @@ -52,6 +54,7 @@ Icon=applications-utilities Comment=Utilities Comment[ar]=الأدوات +Comment[ast]=Utilidaes Comment[bs]=Usluge Comment[ca]=Utilitats Comment[ca@valencia]=Utilitats @@ -76,7 +79,7 @@ Comment[it]=Accessori Comment[ja]=ユーティリティ Comment[ko]=유틸리티 -Comment[lt]=Pagalbininkai +Comment[lt]=Paslaugų programos Comment[nb]=Verktøy Comment[nds]=Warktüüch Comment[nl]=Hulpmiddelen diff --git a/phonon/platform_kde/kdeplatformplugin.cpp b/phonon/platform_kde/kdeplatformplugin.cpp --- a/phonon/platform_kde/kdeplatformplugin.cpp +++ b/phonon/platform_kde/kdeplatformplugin.cpp @@ -61,9 +61,9 @@ const char *actionSlot) const { KNotification *notification = new KNotification(notificationName); - notification->setComponentName(QLatin1Literal("phonon")); + notification->setComponentName(QLatin1String("phonon")); notification->setText(text); - notification->addContext(QLatin1Literal("Application"), + notification->addContext(QLatin1String("Application"), KAboutData::applicationData().componentName()); if (!actions.isEmpty() && receiver && actionSlot) { notification->setActions(actions); @@ -82,7 +82,7 @@ return aboutData.componentName(); } // FIXME: why was this not localized? - return QLatin1Literal("Qt Application"); + return QLatin1String("Qt Application"); } // Phonon4Qt5 internally implements backend lookup an creation. Driving it diff --git a/phonon/platform_kde/phonon.notifyrc b/phonon/platform_kde/phonon.notifyrc --- a/phonon/platform_kde/phonon.notifyrc +++ b/phonon/platform_kde/phonon.notifyrc @@ -2,6 +2,7 @@ IconName=preferences-desktop-sound Comment=Multimedia System Comment[ar]=نظام الوسائط المتعدّدة +Comment[ast]=Sistema multimedia Comment[bg]=Мултимедийна система Comment[bn]=মাল্টিমিডিয়া সিস্টেম Comment[bs]=Multimedijalni sistem @@ -57,7 +58,7 @@ Comment[sr@ijekavianlatin]=Multimedijalni sistem Comment[sr@latin]=Multimedijalni sistem Comment[sv]=Multimediasystem -Comment[tg]=Системаи мултимедиавӣ +Comment[tg]=Низоми мултимедиа Comment[th]=ระบบสื่อประสม Comment[tr]=Çoklu Ortam Sistemi Comment[ug]=كۆپ ۋاسىتە سىستېما @@ -70,6 +71,7 @@ [Context/Application] Name=Application +Name[ast]=Aplicación Name[bs]=Aplikacija Name[ca]=Aplicació Name[ca@valencia]=Aplicació @@ -84,7 +86,7 @@ Name[eu]=Aplikazioak Name[fi]=Sovellus Name[fr]=Application -Name[gl]=Aplicativo +Name[gl]=Aplicación Name[he]=יישומים Name[hr]=Aplikacija Name[hu]=Alkalmazás @@ -95,6 +97,7 @@ Name[ja]=アプリケーション Name[ko]=프로그램 Name[lt]=Programa +Name[lv]=Programma Name[nb]=Program Name[nds]=Programm Name[nl]=Toepassing @@ -111,6 +114,7 @@ Name[sr@ijekavianlatin]=Program Name[sr@latin]=Program Name[sv]=Program +Name[tg]=Барнома Name[tr]=Uygulama Name[uk]=Програма Name[x-test]=xxApplicationxx @@ -152,7 +156,7 @@ Name[kk]=Аудио шығыс құрылғысы өзгерген Name[km]=បាន​ផ្លាស់ប្ដូរ​ឧបករណ៍​បញ្ចូល​អូឌីយ៉ូ Name[ko]=오디오 출력 장치 변경됨 -Name[lt]=Pakeistas garso atgaminimo įrenginys +Name[lt]=Pakeistas garso išvedimo įrenginys Name[lv]=Izmainījās audio izvades ierīce Name[mr]=आवाज आउटपुट देणारे साधन बदलले Name[nb]=Utgangsenhet for lyd er endret @@ -173,7 +177,6 @@ Name[sr@ijekavianlatin]=Promenjen uređaj audio izlaza Name[sr@latin]=Promenjen uređaj audio izlaza Name[sv]=Ljudutenhet ändrades -Name[tg]=Дастгоҳи хуруҷи аудио иваз шуд Name[th]=อุปกรณ์ส่งออกเสียงมีการเปลี่ยนแปลง Name[tr]=Ses Çıktı Aygıtı Değişti Name[ug]=ئۈن چىقىرىش ئۈسكۈنىسى ئۆزگەردى @@ -215,7 +218,7 @@ Comment[kk]=Аудио шығыс құрылғысы автоматты түрде өзгергенде құлақтандыру Comment[km]=ជូន​ដំណឹង នៅ​ពេល​ឧបករណ៍​បញ្ចេញ​អូឌីយ៉ូ​បាន​ផ្លាស់ប្ដូរ​ដោយ​ស្វ័យ​ប្រវត្តិ Comment[ko]=오디오 출력 장치가 자동으로 변경되었을 때 알림 -Comment[lt]=Pranešimai pakeitus garso atgaminimo įrenginį +Comment[lt]=Pranešimas, kai garso išvedimo įrenginys buvo automatiškai pakeistas Comment[lv]=Paziņojums, kad automātiski izmainās audio izvades ierīce Comment[mr]=आवाज आउटपुट देणारे साधन स्वयंचलितरित्या बदलले जाईल तेव्हाची सूचना Comment[nb]=Varsling når utgangsenhet for lyd er endret automatisk @@ -236,7 +239,6 @@ Comment[sr@ijekavianlatin]=Obaveštenje o automatskoj promeni uređaja za audio izlaz Comment[sr@latin]=Obaveštenje o automatskoj promeni uređaja za audio izlaz Comment[sv]=Underrättelse när ljudutenhet har ändrats automatiskt -Comment[tg]=Огоҳдод барои ивазкунии худкори ҳуруҷи дастгоҳи аудиоӣ Comment[th]=แจ้งให้ทราบเมื่ออุปกรณ์ส่วนส่งออกเสียงมีการเปลี่ยนแปลงที่เกิดขึ้นอย่างอัตโนมัติ Comment[tr]=Ses çıktı aygıtı otomatik olarak değiştiğinde çıkan bildirim Comment[ug]=ئۈن چىقىرىش ئۈسكۈنىسى ئۆزلۈكىدىن ئۆزگەرسە ئۇقتۇرىدۇ diff --git a/phonon/platform_kde/phononbackend.desktop b/phonon/platform_kde/phononbackend.desktop --- a/phonon/platform_kde/phononbackend.desktop +++ b/phonon/platform_kde/phononbackend.desktop @@ -9,7 +9,7 @@ Name[bn_IN]=KDE মাল্টিমিডিয়া ব্যাক-এন্ড Name[bs]=KDE‑ova multimedijska pozadina Name[ca]=Dorsal multimèdia del KDE -Name[ca@valencia]=Dorsal multimèdia del KDE +Name[ca@valencia]=Dorsal multimèdia de KDE Name[cs]=Multimediální podpůrná vrstva KDE Name[csb]=Òbsłużënk mùltimediów Name[da]=KDE multimedie-backend @@ -43,7 +43,7 @@ Name[kn]=ಕೆಡಿಇ ಬಹುಮಾಧ್ಯಮ (ಮಲ್ಟಿಮೀಡಿಯಾ) ಹಿಂಬದಿ (ಬ್ಯಾಕ್ ಎಂಡ್) Name[ko]=KDE 멀티미디어 백엔드 Name[ku]=KDE Binesazî ya Pir-medya -Name[lt]=KDE įvairialypės terpės programinė sąsaja +Name[lt]=KDE įvairialypės terpės vidinė pusė Name[lv]=KDE multivides aizmugure Name[mk]=Заден крај за мултимедија во KDE Name[ml]=കെഡിഇ മള്‍ട്ടിമീഡിയ ബാക്കെന്‍ഡ് @@ -69,7 +69,6 @@ Name[sv]=KDE:s multimediagränssnitt Name[ta]=கேபசூ பல்லூடக பின்னணி Name[te]=KDE బహుళమాద్యమం బ్యాక్ఎండ్ -Name[tg]=Пуштибонии мултимедиаи KDE Name[th]=โปรแกรมเบื้องหลังจัดการสื่อประสมของ KDE Name[tr]=KDE Çoklu Ortam Arka Ucu Name[ug]=KDE كۆپ ۋاسىتە ئارقا ئۇچى diff --git a/plasma-windowed/plasma-windowed.desktop b/plasma-windowed/plasma-windowed.desktop --- a/plasma-windowed/plasma-windowed.desktop +++ b/plasma-windowed/plasma-windowed.desktop @@ -56,14 +56,14 @@ Comment[eu]=Exekutatu Plasmoideak aplikazio arrunt gisa Comment[fi]=Suorita plasmoideja sovelluksina Comment[fr]=Lancer les plasmoïdes comme de simples applications -Comment[gl]=Execute plasmoides como se fosen aplicativos sinxelos +Comment[gl]=Execute plasmoides como se fosen aplicacións sinxelas Comment[hu]=Plazmoidok futtatása egyszerű alkalmazásként Comment[id]=Menjalankan Plasmoids sebagai aplikasi sederhana Comment[is]=Keyra Plasmíð sem einföld forrit Comment[it]=Esegui i plasmoidi come semplici applicazioni Comment[ja]=Plasmoid を単純なアプリケーションとして実行 Comment[ko]=Plasmoid를 간단한 프로그램으로 실행 -Comment[lt]=Leisti plazmoidus kaip paprastas programas +Comment[lt]=Paleisti Plasma įskiepius kaip paprastas programas Comment[nb]=Kjør plasmoider som enkle programmer Comment[nds]=Plasma-Lüttprogrammen as eenfach Programmen utföhren Comment[nl]=Plasmoids uitvoeren als eenvoudige toepassingen diff --git a/plasma-workspace.categories b/plasma-workspace.categories --- a/plasma-workspace.categories +++ b/plasma-workspace.categories @@ -1,4 +1,5 @@ org.kde.kf5.ksmserver ksmserver (ksmserver) IDENTIFIER [KSMSERVER] -org.kde.plasma.runner.services DEFAULT_SEVERITY [WARNING] IDENTIFIER [RUNNER_SERVICES] -org.kde.plasma.runner.appstream DEFAULT_SEVERITY [WARNING] IDENTIFIER [RUNNER_APPSTREAM] -org.kde.plasma.runner.bookmarks DEFAULT_SEVERITY [WARNING] IDENTIFIER [RUNNER_BOOKMARKS] +org.kde.plasma.runner.services Plasma Runner Services DEFAULT_SEVERITY [WARNING] IDENTIFIER [RUNNER_SERVICES] +org.kde.plasma.runner.appstream Plasma Runner Appstream DEFAULT_SEVERITY [WARNING] IDENTIFIER [RUNNER_APPSTREAM] +org.kde.plasma.runner.bookmarks Plasma Runner Bookmarks DEFAULT_SEVERITY [WARNING] IDENTIFIER [RUNNER_BOOKMARKS] +org.kde.plasma.dataengine.geolocation Plasma Data engine Geolocation DEFAULT_SEVERITY [WARNING] IDENTIFIER [DATAENGINE_GEOLOCATION] diff --git a/runners/activities/activityrunner.cpp b/runners/activities/activityrunner.cpp --- a/runners/activities/activityrunner.cpp +++ b/runners/activities/activityrunner.cpp @@ -121,7 +121,7 @@ } if (list) { - foreach (const QString &activity, activities) { + for (const QString &activity : qAsConst(activities)) { if (current == activity) { continue; } @@ -134,7 +134,7 @@ } } } else { - foreach (const QString &activity, activities) { + for (const QString &activity : qAsConst(activities)) { if (current == activity) { continue; } @@ -158,7 +158,7 @@ Plasma::QueryMatch match(this); match.setData(activity.id()); match.setType(Plasma::QueryMatch::ExactMatch); - match.setIconName(activity.icon().isEmpty() ? QStringLiteral("preferences-activities") : activity.icon()); + match.setIconName(activity.icon().isEmpty() ? QStringLiteral("activities") : activity.icon()); match.setText(i18n("Switch to \"%1\"", activity.name())); match.setRelevance(0.7 + ((activity.state() == KActivities::Info::Running || activity.state() == KActivities::Info::Starting) ? 0.1 : 0)); diff --git a/runners/activities/plasma-runner-activityrunner.desktop b/runners/activities/plasma-runner-activityrunner.desktop --- a/runners/activities/plasma-runner-activityrunner.desktop +++ b/runners/activities/plasma-runner-activityrunner.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Activities Name[ar]=الأنشطة +Name[ast]=Actividaes Name[bg]=Дейности Name[bs]=Motor aktivnosti Name[ca]=Activitats @@ -49,6 +50,7 @@ Name[sr@ijekavianlatin]=aktivnosti Name[sr@latin]=aktivnosti Name[sv]=Aktiviteter +Name[tg]=Фаъолиятҳо Name[tr]=Etkinlikler Name[ug]=پائالىيەتلەر Name[uk]=Простори дій @@ -61,7 +63,7 @@ Comment[ar]=اسرد أنشطة سطح المكتب وبدّل بينها Comment[bg]=Списък и превключване между дейностите Comment[bs]=Prikaz i prebacivanje između aktivnosti radne površine -Comment[ca]=Llista i commuta entre activitats d'escriptori +Comment[ca]=Llista i commuta entre les activitats d'escriptori Comment[ca@valencia]=Llista i commuta entre activitats d'escriptori Comment[cs]=Zobrazení a přepínač aktivit pracovní plochy Comment[da]=Vis og skift mellem skrivebordsaktiviteter @@ -85,15 +87,15 @@ Comment[kk]=Белсенділіктерді тізімдеу және ауыстыру Comment[km]=មើល ហើយ​ប្ដូរ​ប្លង់​ផ្ទៃតុ​សកម្ម​ Comment[ko]=데스크톱 활동을 보거나 바꾸기 -Comment[lt]=Peržiūrėti ir persijungti tarp darbalaukio veiklų +Comment[lt]=Išvardyti ir perjungti tarp darbalaukio veiklų Comment[lv]=Parādīt un pārslēgties starp virtuālajām darbvirsmām Comment[mr]=वेगळ्या डेस्कटॉप कार्यपध्दतीवर जाण्यासाठी यादी दर्शवा व बदला Comment[nb]=Vis og bytt mellom skrivebordsaktiviteter Comment[nds]=Aktiv Schriefdischakschonen ankieken un wesseln Comment[nl]=Bureaubladactiviteiten laten zien en er tussen schakelen Comment[nn]=Vis og byt mellom skrivebordsaktiviteter Comment[pa]=ਡੈਸਕਟਾਪ ਸਰਗਰਮੀਆਂ ਵੇਖੋ ਅਤੇ ਉਹਨਾਂ ਵਿੱਚ ਜਾਉ -Comment[pl]=Wypisuje i przełącza pomiędzy aktywnościami pulpitu +Comment[pl]=Wypisuje aktywności pulpitu oraz przełącza pomiędzy nimi Comment[pt]=Ver e mudar de actividades no ambiente de trabalho Comment[pt_BR]=Lista e alterna entre as atividades da área de trabalho Comment[ro]=Vizualizează și schimbă între activitățile biroului diff --git a/runners/appstream/appstreamrunner.cpp b/runners/appstream/appstreamrunner.cpp --- a/runners/appstream/appstreamrunner.cpp +++ b/runners/appstream/appstreamrunner.cpp @@ -55,7 +55,7 @@ const auto icons = comp.icons(); if (icons.isEmpty()) { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); - } else foreach(const AppStream::Icon &icon, icons) { + } else for(const AppStream::Icon &icon : icons) { QStringList stock; switch(icon.kind()) { case AppStream::Icon::KindLocal: @@ -82,14 +82,14 @@ if(context.query().size() <= 2) return; - const auto components = findComponentsByString(context.query()); + const auto components = findComponentsByString(context.query()).mid(0, 3); for (const AppStream::Component &component : components) { if (component.kind() != AppStream::Component::KindDesktopApp) continue; const auto idWithoutDesktop = component.id().remove(".desktop"); - const auto serviceQuery = QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName or '%1' in [X-Flatpak-RenamedFrom] or '%2' =~ DesktopEntryName)").arg(component.id(), idWithoutDesktop); + const auto serviceQuery = QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName or (exist [X-Flatpak-RenamedFrom] and ('%1' in [X-Flatpak-RenamedFrom] or '%1;' in [X-Flatpak-RenamedFrom])) or '%2' =~ DesktopEntryName)").arg(component.id(), idWithoutDesktop); const auto servicesFound = KServiceTypeTrader::self()->query(QStringLiteral("Application"), serviceQuery); if (!servicesFound.isEmpty()) diff --git a/runners/appstream/plasma-runner-appstream.desktop b/runners/appstream/plasma-runner-appstream.desktop --- a/runners/appstream/plasma-runner-appstream.desktop +++ b/runners/appstream/plasma-runner-appstream.desktop @@ -1,23 +1,26 @@ [Desktop Entry] Name=Software Center +Name[ast]=Centru de software Name[ca]=Centre de programari Name[ca@valencia]=Centre de programari Name[cs]=Centrum softwaru Name[da]=Softwarecenter Name[de]=Programmverwaltung Name[el]=Κέντρο λογισμικού Name[en_GB]=Software Centre Name[es]=Centro de software +Name[et]=Tarkvarakeskus Name[eu]=Software gunea Name[fi]=Ohjelmakeskus Name[fr]=Logithèque Name[gl]=Centro de Software Name[he]=מרכז התוכנות Name[hu]=Szoftverközpont -Name[id]=Pusat Software +Name[id]=Sentra Perangkat Lunak Name[it]=Software Center Name[ko]=소프트웨어 센터 -Name[lt]=Programų centras +Name[lt]=Programinės įrangos centras +Name[lv]=Programmatūras centrs Name[nl]=Softwarecentrum Name[nn]=Programvaresenter Name[pa]=ਸਾਫਟਵੇਅਰ ਕੇਂਦਰ @@ -38,14 +41,16 @@ Name[zh_CN]=软件中心 Name[zh_TW]=軟體中心 Comment=Lets you find software +Comment[ast]=Déxate alcontrar software Comment[ca]=Permet cercar programari -Comment[ca@valencia]=Permet cercar programari +Comment[ca@valencia]=Permet buscar programari Comment[cs]=Umožní vám hledat software Comment[da]=Hjælper dig med at finde software Comment[de]=Unterstützt die Suche nach Software Comment[el]=Σας δίνει τη δυνατότητα να βρείτε λογισμικό Comment[en_GB]=Lets you find software Comment[es]=Le permite encontrar software +Comment[et]=Võimaldab leida tarkvara Comment[eu]=Softwarea aurkitzen uzten dizu Comment[fi]=Antaa sinun etsiä ohjelmia Comment[fr]=Vous permet de trouver des logiciels @@ -55,7 +60,7 @@ Comment[id]=Mari temukan softwermu Comment[it]=Ti consente di trovare i programmi Comment[ko]=소프트웨어를 찾을 수 있습니다 -Comment[lt]=Leidžia ieškoti programų +Comment[lt]=Leidžia rasti programinę įrangą Comment[nl]=Laat u software vinden Comment[nn]=Lèt deg finna programvare Comment[pa]=ਤੁਹਾਨੂੰ ਸਾਫਟਵੇਅਰ ਲੱਭਣ ਦਿੰਦਾ ਹੈ diff --git a/runners/baloo/plasma-runner-baloosearch.desktop b/runners/baloo/plasma-runner-baloosearch.desktop --- a/runners/baloo/plasma-runner-baloosearch.desktop +++ b/runners/baloo/plasma-runner-baloosearch.desktop @@ -3,7 +3,7 @@ Name[ar]=بحث سطح المكتب Name[bs]=Pretraga radne površine Name[ca]=Cerca a l'escriptori -Name[ca@valencia]=Cerca a l'escriptori +Name[ca@valencia]=Busca en l'escriptori Name[cs]=Vyhledávací služby Name[da]=Skrivebordssøgning Name[de]=Desktop-Suche @@ -22,7 +22,8 @@ Name[it]=Ricerca desktop Name[ja]=デスクトップ検索 Name[ko]=데스크톱 검색 -Name[lt]=Darbalaukio paieška +Name[lt]=Paieška darbalaukyje +Name[lv]=Darbvirsmas meklēšana Name[nb]=Skrivebordssøk Name[nds]=Schriefdischsöök Name[nl]=Bureaubladzoeken @@ -39,14 +40,16 @@ Name[sr@ijekavianlatin]=pretraga radne površi Name[sr@latin]=pretraga radne površi Name[sv]=Skrivbordssökning +Name[tg]=Ҷустуҷӯ дар мизи корӣ Name[tr]=Masaüstü Araması Name[uk]=Стільничний пошук Name[x-test]=xxDesktop Searchxx Name[zh_CN]=桌面搜索 Name[zh_TW]=桌面搜尋 Comment=Searches through files, emails and contacts +Comment[ast]=Gueta pente ficheros, correos y contautos Comment[ca]=Cerca pels fitxers, correus electrònics i contactes -Comment[ca@valencia]=Cerca pels fitxers, correus electrònics i contactes +Comment[ca@valencia]=Busca pels fitxers, correus electrònics i contactes Comment[cs]=Prohledává v souborech, emailech a kontaktech Comment[da]=Gennemsøger filer, e-mails og kontakter Comment[de]=Durchsucht Dateien, E-Mails und Kontakte @@ -62,7 +65,7 @@ Comment[id]=Pencarian melalui file, email dan kontak Comment[it]=Cerca tra i file, i messaggi di posta ed i contatti Comment[ko]=파일, 이메일, 연락처 검색 -Comment[lt]=Ieško failuose, el. laiškuose ir kontaktuose +Comment[lt]=Atlieka paiešką failuose, el. laiškuose ir adresatuose Comment[nl]=Doorzoekt bestanden, e-mailberichten en contactpersonen Comment[nn]=Søk gjennom filer, e-postar og kontaktar Comment[pa]=ਫ਼ਾਇਲਾਂ, ਈਮੇਲਾਂ ਅਤੇ ਸੰਪਰਕਾਂ 'ਚੋਂ ਲੱਭੋ diff --git a/runners/bookmarks/bookmarksrunner.cpp b/runners/bookmarks/bookmarksrunner.cpp --- a/runners/bookmarks/bookmarksrunner.cpp +++ b/runners/bookmarks/bookmarksrunner.cpp @@ -82,8 +82,8 @@ bool allBookmarks = term.compare(i18nc("list of all konqueror bookmarks", "bookmarks"), Qt::CaseInsensitive) == 0; - QList matches = m_browser->match(term, allBookmarks); - foreach(BookmarkMatch match, matches) { + const QList matches = m_browser->match(term, allBookmarks); + for(BookmarkMatch match : matches) { if(!context.isValid()) return; context.addMatch(match.asQueryMatch(this)); @@ -114,7 +114,7 @@ const QString term = action.data().toString(); QUrl url = QUrl(term); - //support urls like "kde.org" by transforming them to http://kde.org + //support urls like "kde.org" by transforming them to https://kde.org if (url.scheme().isEmpty()) { const int idx = term.indexOf('/'); diff --git a/runners/bookmarks/browserfactory.cpp b/runners/bookmarks/browserfactory.cpp --- a/runners/bookmarks/browserfactory.cpp +++ b/runners/bookmarks/browserfactory.cpp @@ -33,13 +33,13 @@ } delete m_previousBrowser; m_previousBrowserName = browserName; - if (browserName.contains(QStringLiteral("firefox"), Qt::CaseInsensitive) || browserName.contains(QStringLiteral("iceweasel"), Qt::CaseInsensitive)) { + if (browserName.contains(QLatin1String("firefox"), Qt::CaseInsensitive) || browserName.contains(QLatin1String("iceweasel"), Qt::CaseInsensitive)) { m_previousBrowser = new Firefox(parent); - } else if (browserName.contains(QStringLiteral("opera"), Qt::CaseInsensitive)) { + } else if (browserName.contains(QLatin1String("opera"), Qt::CaseInsensitive)) { m_previousBrowser = new Opera(parent); - } else if (browserName.contains(QStringLiteral("chrome"), Qt::CaseInsensitive)) { + } else if (browserName.contains(QLatin1String("chrome"), Qt::CaseInsensitive)) { m_previousBrowser = new Chrome(new FindChromeProfile(QStringLiteral("google-chrome"), QDir::homePath(), parent), parent); - } else if (browserName.contains(QStringLiteral("chromium"), Qt::CaseInsensitive)) { + } else if (browserName.contains(QLatin1String("chromium"), Qt::CaseInsensitive)) { m_previousBrowser = new Chrome(new FindChromeProfile(QStringLiteral("chromium"), QDir::homePath(), parent), parent); } else { m_previousBrowser = new KDEBrowser(parent); diff --git a/runners/bookmarks/browsers/chrome.cpp b/runners/bookmarks/browsers/chrome.cpp --- a/runners/bookmarks/browsers/chrome.cpp +++ b/runners/bookmarks/browsers/chrome.cpp @@ -34,7 +34,7 @@ class ProfileBookmarks { public: - ProfileBookmarks(Profile &profile) : m_profile(profile) {} + ProfileBookmarks(const Profile &profile) : m_profile(profile) {} inline QJsonArray bookmarks() { return m_bookmarks; } inline Profile profile() { return m_profile; } void tearDown() { m_profile.favicon()->teardown(); clear(); } @@ -50,16 +50,17 @@ m_watcher(new KDirWatch(this)), m_dirty(false) { - foreach(Profile profile, findProfile->find()) { + const auto profiles = findProfile->find(); + for(const Profile &profile : profiles) { m_profileBookmarks << new ProfileBookmarks(profile); m_watcher->addFile(profile.path()); } connect(m_watcher, &KDirWatch::created, [=] { m_dirty = true; }); } Chrome::~Chrome() { - foreach(ProfileBookmarks *profileBookmark, m_profileBookmarks) { + for(ProfileBookmarks *profileBookmark : qAsConst(m_profileBookmarks)) { delete profileBookmark; } } @@ -70,7 +71,7 @@ prepare(); } QList results; - foreach(ProfileBookmarks *profileBookmarks, m_profileBookmarks) { + for(ProfileBookmarks *profileBookmarks : qAsConst(m_profileBookmarks)) { results << match(term, addEveryThing, profileBookmarks); } return results; @@ -95,7 +96,7 @@ void Chrome::prepare() { m_dirty = false; - foreach(ProfileBookmarks *profileBookmarks, m_profileBookmarks) { + for(ProfileBookmarks *profileBookmarks : qAsConst(m_profileBookmarks)) { Profile profile = profileBookmarks->profile(); profileBookmarks->clear(); QFile bookmarksFile(profile.path()); @@ -107,7 +108,7 @@ continue; } const QJsonObject resultMap = jdoc.object(); - if (!resultMap.contains(QStringLiteral("roots"))) { + if (!resultMap.contains(QLatin1String("roots"))) { return; } const QJsonObject entries = resultMap.value(QStringLiteral("roots")).toObject(); @@ -120,7 +121,7 @@ void Chrome::teardown() { - foreach(ProfileBookmarks *profileBookmarks, m_profileBookmarks) { + for(ProfileBookmarks *profileBookmarks : qAsConst(m_profileBookmarks)) { profileBookmarks->tearDown(); } } diff --git a/runners/bookmarks/browsers/chromefindprofile.cpp b/runners/bookmarks/browsers/chromefindprofile.cpp --- a/runners/bookmarks/browsers/chromefindprofile.cpp +++ b/runners/bookmarks/browsers/chromefindprofile.cpp @@ -58,7 +58,7 @@ QVariantMap localState = jdoc.object().toVariantMap(); QVariantMap profilesConfig = localState.value(QStringLiteral("profile")).toMap().value(QStringLiteral("info_cache")).toMap(); - foreach(const QString &profile, profilesConfig.keys()) { + for(const QString &profile : profilesConfig.keys()) { const QString profilePath = QStringLiteral("%1/%2").arg(configDirectory, profile); const QString profileBookmarksPath = QStringLiteral("%1/%2").arg(profilePath, QStringLiteral("Bookmarks")); profiles << Profile(profileBookmarksPath, FaviconFromBlob::chrome(profilePath, this)); diff --git a/runners/bookmarks/browsers/firefox.cpp b/runners/bookmarks/browsers/firefox.cpp --- a/runners/bookmarks/browsers/firefox.cpp +++ b/runners/bookmarks/browsers/firefox.cpp @@ -84,23 +84,23 @@ } //qDebug() << "Firefox bookmark: match " << term; - QString tmpTerm = term; QString query; if (addEverything) { query = QStringLiteral("SELECT moz_bookmarks.fk, moz_bookmarks.title, moz_places.url " \ "FROM moz_bookmarks, moz_places WHERE " \ "moz_bookmarks.type = 1 AND moz_bookmarks.fk = moz_places.id"); } else { - const QString escapedTerm = tmpTerm.replace('\'', QLatin1String("\\'")); query = QString("SELECT moz_bookmarks.fk, moz_bookmarks.title, moz_places.url " \ "FROM moz_bookmarks, moz_places WHERE " \ "moz_bookmarks.type = 1 AND moz_bookmarks.fk = moz_places.id AND " \ - "(moz_bookmarks.title LIKE '%" + escapedTerm + "%' or moz_places.url LIKE '%" - + escapedTerm + "%')"); + "(moz_bookmarks.title LIKE :term OR moz_places.url LIKE :term)"); } - QList results = m_fetchsqlite->query(query, QMap()); + const QMap bindVariables { + {QStringLiteral(":term"), QStringLiteral("%%%1%%").arg(term)}, + }; + const QList results = m_fetchsqlite->query(query, bindVariables); QMultiMap uniqueResults; - foreach(QVariantMap result, results) { + for(const QVariantMap &result : results) { const QString title = result.value(QStringLiteral("title")).toString(); const QUrl url = result.value(QStringLiteral("url")).toUrl(); if (url.isEmpty() || url.scheme() == QLatin1String("place")) { diff --git a/runners/bookmarks/browsers/opera.cpp b/runners/bookmarks/browsers/opera.cpp --- a/runners/bookmarks/browsers/opera.cpp +++ b/runners/bookmarks/browsers/opera.cpp @@ -39,18 +39,18 @@ QLatin1String descriptionStart("\tDESCRIPTION="); // search - foreach (const QString & entry, m_operaBookmarkEntries) { + for (const QString & entry : qAsConst(m_operaBookmarkEntries)) { QStringList entryLines = entry.split(QStringLiteral("\n")); - if (!entryLines.first().startsWith(QStringLiteral("#URL"))) { + if (!entryLines.first().startsWith(QLatin1String("#URL"))) { continue; // skip folder entries } entryLines.pop_front(); QString name; QString url; QString description; - foreach (const QString & line, entryLines) { + for (const QString & line : qAsConst(entryLines)) { if (line.startsWith(nameStart)) { name = line.mid( QString(nameStart).length() ).simplified(); } else if (line.startsWith(urlStart)) { diff --git a/runners/bookmarks/faviconfromblob.cpp b/runners/bookmarks/faviconfromblob.cpp --- a/runners/bookmarks/faviconfromblob.cpp +++ b/runners/bookmarks/faviconfromblob.cpp @@ -43,7 +43,7 @@ FetchSqlite *fetchSqlite = new FetchSqlite(profileDirectory + QStringLiteral("/Favicons"), faviconCache, parent); QString faviconQuery; - if(fetchSqlite->tables().contains(QStringLiteral("favicon_bitmaps"))) { + if(fetchSqlite->tables().contains(QLatin1String("favicon_bitmaps"))) { faviconQuery = QLatin1String("SELECT * FROM favicons " \ "inner join icon_mapping on icon_mapping.icon_id = favicons.id " \ "inner join favicon_bitmaps on icon_mapping.icon_id = favicon_bitmaps.icon_id " \ diff --git a/runners/bookmarks/fetchsqlite.cpp b/runners/bookmarks/fetchsqlite.cpp --- a/runners/bookmarks/fetchsqlite.cpp +++ b/runners/bookmarks/fetchsqlite.cpp @@ -97,8 +97,8 @@ //qDebug() << "query: " << sql; QSqlQuery query(db); query.prepare(sql); - foreach(const QString &variableName, bindObjects.keys()) { - query.bindValue(variableName, bindObjects.value(variableName)); + for (auto entry = bindObjects.constKeyValueBegin(); entry != bindObjects.constKeyValueEnd(); ++entry) { + query.bindValue((*entry).first, (*entry).second); //qDebug() << "* Bound " << variableName << " to " << query.boundValue(variableName); } diff --git a/runners/bookmarks/plasma-runner-bookmarks.desktop b/runners/bookmarks/plasma-runner-bookmarks.desktop --- a/runners/bookmarks/plasma-runner-bookmarks.desktop +++ b/runners/bookmarks/plasma-runner-bookmarks.desktop @@ -2,6 +2,7 @@ # ctxt: plasma runner Name=Bookmarks Name[ar]=العلامات +Name[ast]=Marcadores Name[bg]=Отметки Name[bn]=বুকমার্ক Name[bs]=Zabilješke @@ -59,7 +60,7 @@ Name[sr@ijekavianlatin]=obeleživači Name[sr@latin]=obeleživači Name[sv]=Bokmärken -Name[tg]=Хатчӯбмонӣ +Name[tg]=Хатчӯбҳо Name[th]=ที่คั่นหน้า Name[tr]=Yer İmleri Name[ug]=خەتكۈشلەر @@ -71,13 +72,14 @@ Name[zh_TW]=書籤 Comment=Find and open bookmarks Comment[ar]=اعثر على العلامات وافتحها +Comment[ast]=Alcuentra y abre marcadores Comment[be@latin]=Pošuk i adčynieńnie zakładak. Comment[bg]=Търсене и зареждане на отметки Comment[bn]=বুকমার্ক খোঁজো এবং খোলো Comment[bn_IN]=বুকমার্ক অনুসন্ধান করুন ও খুলুন Comment[bs]=Nađite i otvorite zabilješku Comment[ca]=Cerca i obre adreces d'interès -Comment[ca@valencia]=Cerca i obri adreces d'interés +Comment[ca@valencia]=Busca i obri adreces d'interés Comment[cs]=Najít a otevřít záložku Comment[csb]=Malézë ë òtemkni załóżczi Comment[da]=Find og åbn bogmærker @@ -109,7 +111,7 @@ Comment[kn]=ಅಂಕನಗಳನ್ನು (ಬುಕ್ ಮಾರ್ಕ್) ಹುಡುಕಿ ತೆರೆ Comment[ko]=책갈피를 탐색하고 열기 Comment[ku]=Bijareyan bibîne û veke -Comment[lt]=Rasti ir atverti žymelę +Comment[lt]=Rasti ir atverti žymeles Comment[lv]=Meklēt un atvērt grāmatzīmes Comment[mk]=Пронајдете и отворете обележувачи Comment[ml]=അടയാളക്കുറിപ്പുകള്‍ കണ്ടെത്തി തുറക്കുക @@ -135,7 +137,6 @@ Comment[sv]=Sök efter och öppna bokmärken Comment[ta]=Find and open bookmarks Comment[te]=పేజిగుర్తులను కనుగొనుము మరియు తెరువుము -Comment[tg]=Ҷустуҷӯи замимаҳо Comment[th]=ค้นหาและเปิดคั่นหน้า Comment[tr]=Yer imlerini bul ve aç Comment[ug]=خەتكۈشلەرنى ئىزدە ۋە ئاچ diff --git a/runners/bookmarks/tests/testchromebookmarks.cpp b/runners/bookmarks/tests/testchromebookmarks.cpp --- a/runners/bookmarks/tests/testchromebookmarks.cpp +++ b/runners/bookmarks/tests/testchromebookmarks.cpp @@ -82,18 +82,18 @@ chrome->prepare(); QList matches = chrome->match("any", true); QCOMPARE(matches.size(), 3); - verifyMatch(matches[0], "some bookmark in bookmark bar", "http://somehost.com/", 0.18, QueryMatch::PossibleMatch); - verifyMatch(matches[1], "bookmark in other bookmarks", "http://otherbookmarks.com/", 0.18, QueryMatch::PossibleMatch); - verifyMatch(matches[2], "bookmark in somefolder", "http://somefolder.com/", 0.18, QueryMatch::PossibleMatch); + verifyMatch(matches[0], "some bookmark in bookmark bar", "https://somehost.com/", 0.18, QueryMatch::PossibleMatch); + verifyMatch(matches[1], "bookmark in other bookmarks", "https://otherbookmarks.com/", 0.18, QueryMatch::PossibleMatch); + verifyMatch(matches[2], "bookmark in somefolder", "https://somefolder.com/", 0.18, QueryMatch::PossibleMatch); } void TestChromeBookmarks::itShouldFindOnlyMatches() { Chrome *chrome = new Chrome(m_findBookmarksInCurrentDirectory.data(), this); chrome->prepare(); QList matches = chrome->match("other", false); QCOMPARE(matches.size(), 1); - verifyMatch(matches[0], "bookmark in other bookmarks", "http://otherbookmarks.com/", 0.45, QueryMatch::PossibleMatch); + verifyMatch(matches[0], "bookmark in other bookmarks", "https://otherbookmarks.com/", 0.45, QueryMatch::PossibleMatch); } void TestChromeBookmarks::itShouldClearResultAfterCallingTeardown() @@ -115,10 +115,10 @@ chrome->prepare(); QList matches = chrome->match("any", true); QCOMPARE(matches.size(), 4); - verifyMatch(matches[0], "some bookmark in bookmark bar", "http://somehost.com/", 0.18, QueryMatch::PossibleMatch); - verifyMatch(matches[1], "bookmark in other bookmarks", "http://otherbookmarks.com/", 0.18, QueryMatch::PossibleMatch); - verifyMatch(matches[2], "bookmark in somefolder", "http://somefolder.com/", 0.18, QueryMatch::PossibleMatch); - verifyMatch(matches[3], "bookmark in secondProfile", "http://secondprofile.com/", 0.18, QueryMatch::PossibleMatch); + verifyMatch(matches[0], "some bookmark in bookmark bar", "https://somehost.com/", 0.18, QueryMatch::PossibleMatch); + verifyMatch(matches[1], "bookmark in other bookmarks", "https://otherbookmarks.com/", 0.18, QueryMatch::PossibleMatch); + verifyMatch(matches[2], "bookmark in somefolder", "https://somefolder.com/", 0.18, QueryMatch::PossibleMatch); + verifyMatch(matches[3], "bookmark in secondProfile", "https://secondprofile.com/", 0.18, QueryMatch::PossibleMatch); } diff --git a/runners/calculator/CMakeLists.txt b/runners/calculator/CMakeLists.txt --- a/runners/calculator/CMakeLists.txt +++ b/runners/calculator/CMakeLists.txt @@ -5,7 +5,7 @@ find_package(Qalculate) set_package_properties(Qalculate PROPERTIES DESCRIPTION "Qalculate Library" - URL "http://qalculate.sourceforge.net" + URL "https://qalculate.github.io/" TYPE OPTIONAL PURPOSE "Needed to enable advanced features of the calculator runner" ) diff --git a/runners/calculator/calculatorrunner.cpp b/runners/calculator/calculatorrunner.cpp --- a/runners/calculator/calculatorrunner.cpp +++ b/runners/calculator/calculatorrunner.cpp @@ -72,19 +72,19 @@ void CalculatorRunner::powSubstitutions(QString& cmd) { - if (cmd.contains(QStringLiteral("e+"), Qt::CaseInsensitive)) { - cmd = cmd.replace(QLatin1String("e+"), QLatin1String("*10^"), Qt::CaseInsensitive); + if (cmd.contains(QLatin1String("e+"), Qt::CaseInsensitive)) { + cmd.replace(QLatin1String("e+"), QLatin1String("*10^"), Qt::CaseInsensitive); } - if (cmd.contains(QStringLiteral("e-"), Qt::CaseInsensitive)) { - cmd = cmd.replace(QLatin1String("e-"), QLatin1String("*10^-"), Qt::CaseInsensitive); + if (cmd.contains(QLatin1String("e-"), Qt::CaseInsensitive)) { + cmd.replace(QLatin1String("e-"), QLatin1String("*10^-"), Qt::CaseInsensitive); } // the below code is scary mainly because we have to honor priority // honor decimal numbers and parenthesis. while (cmd.contains(QLatin1Char('^'))) { int where = cmd.indexOf(QLatin1Char('^')); - cmd = cmd.replace(where, 1, QLatin1Char(',')); + cmd.replace(where, 1, QLatin1Char(',')); int preIndex = where - 1; int postIndex = where + 1; int count = 0; @@ -158,16 +158,16 @@ void CalculatorRunner::hexSubstitutions(QString& cmd) { - if (cmd.contains(QStringLiteral("0x"))) { + if (cmd.contains(QLatin1String("0x"))) { //Append +0 so that the calculator can serve also as a hex converter cmd.append(QLatin1String("+0")); bool ok; int pos = 0; QString hex; - while (cmd.contains(QStringLiteral("0x"))) { + while (cmd.contains(QLatin1String("0x"))) { hex.clear(); - pos = cmd.indexOf(QStringLiteral("0x"), pos); + pos = cmd.indexOf(QLatin1String("0x"), pos); for (int q = 0; q < cmd.size(); q++) {//find end of hex number QChar current = cmd[pos+q+2]; @@ -187,22 +187,22 @@ void CalculatorRunner::userFriendlySubstitutions(QString& cmd) { if (cmd.contains(QLocale().decimalPoint(), Qt::CaseInsensitive)) { - cmd=cmd.replace(QLocale().decimalPoint(), QLatin1Char('.'), Qt::CaseInsensitive); + cmd.replace(QLocale().decimalPoint(), QLatin1Char('.'), Qt::CaseInsensitive); } // the following substitutions are not needed with libqalculate #ifndef ENABLE_QALCULATE hexSubstitutions(cmd); powSubstitutions(cmd); if (cmd.contains(QRegExp(QStringLiteral("\\d+and\\d+")))) { - cmd = cmd.replace(QRegExp(QStringLiteral("(\\d+)and(\\d+)")), QStringLiteral("\\1&\\2")); + cmd.replace(QRegExp(QStringLiteral("(\\d+)and(\\d+)")), QStringLiteral("\\1&\\2")); } if (cmd.contains(QRegExp(QStringLiteral("\\d+or\\d+")))) { - cmd = cmd.replace(QRegExp(QStringLiteral("(\\d+)or(\\d+)")), QStringLiteral("\\1|\\2")); + cmd.replace(QRegExp(QStringLiteral("(\\d+)or(\\d+)")), QStringLiteral("\\1|\\2")); } if (cmd.contains(QRegExp(QStringLiteral("\\d+xor\\d+")))) { - cmd = cmd.replace(QRegExp(QStringLiteral("(\\d+)xor(\\d+)")), QStringLiteral("\\1^\\2")); + cmd.replace(QRegExp(QStringLiteral("(\\d+)xor(\\d+)")), QStringLiteral("\\1^\\2")); } #endif } @@ -268,7 +268,7 @@ QString result = calculate(cmd, &isApproximate); if (!result.isEmpty() && result != cmd) { if (toHex) { - result = QStringLiteral("0x") + QString::number(result.toInt(), 16).toUpper(); + result = QLatin1String("0x") + QString::number(result.toInt(), 16).toUpper(); } Plasma::QueryMatch match(this); diff --git a/runners/calculator/plasma-runner-calculator.desktop b/runners/calculator/plasma-runner-calculator.desktop --- a/runners/calculator/plasma-runner-calculator.desktop +++ b/runners/calculator/plasma-runner-calculator.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Calculator Name[ar]=آلة حاسبة +Name[ast]=Calculadora Name[be@latin]=Kalkulatar Name[bg]=Калкулатор Name[bn]=ক্যালকুলেটর @@ -67,7 +68,7 @@ Name[sv]=Miniräknare Name[ta]=Calculator Name[te]=గణనపరికరం -Name[tg]=Калкулятор +Name[tg]=Ҳисобкунак Name[th]=เครื่องคิดเลข Name[tr]=Hesap Makinesi Name[ug]=ھېسابلىغۇچ @@ -81,6 +82,7 @@ Name[zh_TW]=計算機 Comment=Calculate expressions Comment[ar]=احسب التعابير +Comment[ast]=Calcula espresiones Comment[be@latin]=Padlik vyrazaŭ. Comment[bg]=Изчисления Comment[bs]=Računajte izraze @@ -131,7 +133,7 @@ Comment[nn]=Grafisk kalkulator Comment[or]=ଅଭିବ୍ୟକ୍ତିଗୁଡ଼ିକୁ ଗଣନା କରନ୍ତୁ Comment[pa]=ਸਮੀਕਰਨ ਕੱਢੋ -Comment[pl]=Oblicza wyrażenia +Comment[pl]=Oblicza wyrażenia matematyczne Comment[pt]=Calcular expressões Comment[pt_BR]=Calcula expressões Comment[ro]=Calculează expresii @@ -147,7 +149,6 @@ Comment[sv]=Beräkna uttryck Comment[ta]=Calculate expressions Comment[te]=సమాసాలను గణించుము -Comment[tg]=Вычисление выражений Comment[th]=คำนวณนิพจน์ Comment[tr]=İfadeleri hesapla Comment[ug]=ئىپادىنى ھېسابلا diff --git a/runners/calculator/qalculate_engine.cpp b/runners/calculator/qalculate_engine.cpp --- a/runners/calculator/qalculate_engine.cpp +++ b/runners/calculator/qalculate_engine.cpp @@ -66,7 +66,7 @@ QUrl dest = QUrl::fromLocalFile(QFile::decodeName(CALCULATOR->getExchangeRatesFileName().c_str())); KIO::Job* getJob = KIO::file_copy(source, dest, -1, KIO::Overwrite | KIO::HideProgressInfo); - connect( getJob, SIGNAL(result(KJob*)), this, SLOT(updateResult(KJob*)) ); + connect( getJob, &KJob::result, this, &QalculateEngine::updateResult ); } void QalculateEngine::updateResult(KJob* job) @@ -113,6 +113,9 @@ po.negative_exponents = false; po.lower_case_e = true; po.base_display = BASE_DISPLAY_NORMAL; + #if defined(QALCULATE_MAJOR_VERSION) && defined(QALCULATE_MINOR_VERSION) && (QALCULATE_MAJOR_VERSION > 2 || (QALCULATE_MAJOR_VERSION == 2 && QALCULATE_MINOR_VERSION >= 2)) + po.interval_display = INTERVAL_DISPLAY_SIGNIFICANT_DIGITS; + #endif result.format(po); diff --git a/runners/kill/killrunner.cpp b/runners/kill/killrunner.cpp --- a/runners/kill/killrunner.cpp +++ b/runners/kill/killrunner.cpp @@ -120,7 +120,7 @@ QList matches; const QList processlist = m_processes->getAllProcesses(); - foreach (const KSysGuard::Process *process, processlist) { + for (const KSysGuard::Process *process : processlist) { if (!context.isValid()) { return; } diff --git a/runners/kill/killrunner_config.cpp b/runners/kill/killrunner_config.cpp --- a/runners/kill/killrunner_config.cpp +++ b/runners/kill/killrunner_config.cpp @@ -19,6 +19,7 @@ //Project-Includes #include "killrunner_config.h" +#include //KDE-Includes #include #include @@ -42,10 +43,15 @@ m_ui->sorting->addItem(i18n("CPU usage"), CPU); m_ui->sorting->addItem(i18n("inverted CPU usage"), CPUI); m_ui->sorting->addItem(i18n("nothing"), NONE); - - connect(m_ui->useTriggerWord,SIGNAL(stateChanged(int)),this,SLOT(changed())); - connect(m_ui->triggerWord,SIGNAL(textChanged(QString)),this,SLOT(changed())); - connect(m_ui->sorting,SIGNAL(currentIndexChanged(int)),this,SLOT(changed())); +#if KCONFIGWIDGETS_VERSION < QT_VERSION_CHECK(5, 64, 0) + connect(m_ui->useTriggerWord, &QCheckBox::stateChanged, this, QOverload<>::of(&KillRunnerConfig::changed)); + connect(m_ui->triggerWord, &KLineEdit::textChanged, this, QOverload<>::of(&KillRunnerConfig::changed)); + connect(m_ui->sorting, QOverload::of(&QComboBox::currentIndexChanged), this, QOverload<>::of(&KillRunnerConfig::changed)); +#else + connect(m_ui->useTriggerWord, &QCheckBox::stateChanged, this, &KillRunnerConfig::markAsChanged); + connect(m_ui->triggerWord, &KLineEdit::textChanged, this, &KillRunnerConfig::markAsChanged); + connect(m_ui->sorting, QOverload::of(&QComboBox::currentIndexChanged), this, &KillRunnerConfig::markAsChanged); +#endif load(); } diff --git a/runners/kill/plasma-runner-kill.desktop b/runners/kill/plasma-runner-kill.desktop --- a/runners/kill/plasma-runner-kill.desktop +++ b/runners/kill/plasma-runner-kill.desktop @@ -21,7 +21,7 @@ Name[fr]=Terminer des applications Name[fy]=Programma's ôfbrekke Name[ga]=Scoir ó Fheidhmchláir -Name[gl]=Terminar aplicativos +Name[gl]=Terminar aplicacións Name[gu]=કાર્યક્રમો બંધ કરો Name[he]=סיום פעולת יישומים Name[hi]=अनुप्रयोग खतम करो @@ -36,7 +36,7 @@ Name[km]=កម្មវិធី​ស្ថានីយ Name[kn]=ಅನ್ವಯಗಳನ್ನು ಅಂತ್ಯಗೊಳಿಸು Name[ko]=프로그램 끝내기 -Name[lt]=Nutraukti programas +Name[lt]=Baigti programų darbą Name[lv]=Pārtraukt programmas Name[mk]=Запирање на апликации Name[ml]=പ്രയോഗങ്ങളെ ഇല്ലാതാക്കുക @@ -59,7 +59,6 @@ Name[sr@ijekavianlatin]=obustavljanje programa Name[sr@latin]=obustavljanje programa Name[sv]=Avsluta program -Name[tg]=Барномаҳои терминал Name[th]=ยุติการทำงานของโปรแกรม Name[tr]=Uygulamaları Bitir Name[ug]=پروگراممىلارنى توختات @@ -72,8 +71,8 @@ Comment[ar]=أوقف التطبيقات التي تعمل حاليًا Comment[bg]=Спиране на работещи в момента програми Comment[bs]=Zaustavlja programe trenutno u pogonu -Comment[ca]=Atura aplicacions que estan actualment en execució -Comment[ca@valencia]=Atura aplicacions que estan actualment en execució +Comment[ca]=Atura les aplicacions que estan en execució +Comment[ca@valencia]=Para aplicacions que estan actualment en execució Comment[cs]=Ukončit aktuálně spuštěné aplikace Comment[csb]=Zamëkô prawie robiącé aplikacëje Comment[da]=Stop programmer der kører i øjeblikket @@ -87,20 +86,20 @@ Comment[fr]=Arrête des applications en cours d'exécution Comment[fy]=Lit applikaasje dy't no rinne ophâlde Comment[ga]=Scoir ó fheidhmchláir atá ag rith faoi láthair -Comment[gl]=Detén aplicativos que están a executarse +Comment[gl]=Detén aplicacións que están a executarse Comment[he]=עצירת יישומים הפועלים כעת Comment[hr]=Zaustavi aplikacije koje su trenutno pokrenute Comment[hu]=Jelenleg futó alkalmazások leállítása Comment[ia]=Stoppa applicationes que il es currentemente executante -Comment[id]=Stop aplikasi yang saat ini tengah berjalan +Comment[id]=Menghentikan aplikasi yang saat ini tengah berjalan Comment[is]=Stöðva forrit sem eru keyrandi Comment[it]=Ferma le applicazioni attualmente in esecuzione Comment[ja]=実行中のアプリケーションを停止します Comment[kk]=Орындалып жатқан қолданбаларды тоқтату Comment[km]=បញ្ឈប់​កម្មវិធី​ដែលបច្ចុប្បន្ន​កំពុង​តែ​រត់ Comment[kn]=ಪ್ರಸ್ತುತ ಚಾಲನೆಯಲ್ಲಿರುವ ಅನ್ವಯಗಳನ್ನು ನಿಲ್ಲಿಸು Comment[ko]=현재 실행 중인 프로그램을 끝냅니다 -Comment[lt]=Sustabdo programas, kurios šiuo metu veikia +Comment[lt]=Stabdyti šiuo metu veikiančias programas Comment[lv]=Apturēt šobrīd darbojošās programmas Comment[mk]=Ги запира апликациите што моментално се активни Comment[ml]=ഇപ്പോള്‍ പ്രവര്‍ത്തിയ്ക്കുന്ന പ്രയോഗങ്ങള്‍ നിര്‍ത്തുക @@ -123,7 +122,6 @@ Comment[sr@ijekavianlatin]=Zaustavlja programe trenutno u pogonu Comment[sr@latin]=Zaustavlja programe trenutno u pogonu Comment[sv]=Stoppa program som för närvarande kör -Comment[tg]=Қатъ кардани барномаҳои ҷорӣ Comment[th]=หยุดการทำงานของโปรแกรมที่กำลังทำงานอยู่ในปัจจุบัน Comment[tr]=Şu anda çalışan uygulamaları durdur Comment[ug]=نۆۋەتتە ئىجرا بولۇۋاتقان پروگراممىلارنى توختىتىدۇ diff --git a/runners/kill/plasma-runner-kill_config.desktop b/runners/kill/plasma-runner-kill_config.desktop --- a/runners/kill/plasma-runner-kill_config.desktop +++ b/runners/kill/plasma-runner-kill_config.desktop @@ -27,7 +27,7 @@ Name[fr]=Tuer des applications Name[fy]=Programma's ôfbrekke Name[ga]=Maraigh Feidhmchláir -Name[gl]=Matar aplicativos +Name[gl]=Matar aplicacións Name[gu]=કાર્યક્રમો બંધ કરો Name[he]=הריגת יישומים Name[hi]=अनुप्रयोग खतम करो @@ -42,7 +42,7 @@ Name[km]=បញ្ឈប់​កម្មវិធី Name[kn]=ಅನ್ವಯಗಳನ್ನು ಅಂತ್ಯಗೊಳಿಸು Name[ko]=프로그램 죽이기 -Name[lt]=Užverti programas +Name[lt]=Nutraukti programas Name[lv]=Nokaut programmas Name[mk]=Запирање на апликации Name[ml]=പ്രയോഗങ്ങളെ കൊല്ലുക @@ -65,7 +65,6 @@ Name[sr@ijekavianlatin]=obustavljanje programa Name[sr@latin]=obustavljanje programa Name[sv]=Döda program -Name[tg]=Барномаҳои KDE Name[th]=ฆ่าโพรเซสของโปรแกรม Name[tr]=Uygulamaları Sonlandır Name[ug]=پروگراممىنى ئاخىرلاشتۇر diff --git a/runners/locations/plasma-runner-locations.desktop b/runners/locations/plasma-runner-locations.desktop --- a/runners/locations/plasma-runner-locations.desktop +++ b/runners/locations/plasma-runner-locations.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Locations Name[ar]=المواقع +Name[ast]=Allugamientos Name[be@latin]=Miescy Name[bg]=Местоположения Name[bn]=অবস্থান @@ -65,7 +66,7 @@ Name[sv]=Platser Name[ta]=Locations Name[te]=స్థానములు -Name[tg]=Ҷойгиршавӣ +Name[tg]=Ҷойгиршавиҳо Name[th]=ตำแหน่งที่อยู่ Name[tr]=Konumlar Name[ug]=ئورنى @@ -77,6 +78,7 @@ Name[zh_TW]=位置 Comment=File and URL opener Comment[ar]=فاتح ملفات ووصلات +Comment[ast]=Abridor de ficheros y URLs Comment[be@latin]=Adčynieńnie fajłaŭ i adrasoŭ Comment[bg]=Отваряне на файлове и адреси Comment[bn_IN]=ফাইল ও URL প্রদর্শক @@ -114,8 +116,8 @@ Comment[kn]=ಕಡತ ಮತ್ತು URL ತೆರೆಯುಗ Comment[ko]=파일과 URL을 여는 도구 Comment[ku]=Vekera pel û URL'an -Comment[lt]=Failų ir URL atvėriklis -Comment[lv]=Failu un URL atvērējs +Comment[lt]=Failų ir URL atvėrimo įrankis +Comment[lv]=Datņu un URL atvērējs Comment[mk]=Отворач на датотеки и адреси Comment[ml]=ഫയല്‍, യുആര്‍എല്‍ തുറക്കുന്നതിനുള്ള സംവിധാനം Comment[mr]=फाईल व URL ओपनर @@ -140,7 +142,6 @@ Comment[sv]=Öppna filer och webbadresser Comment[ta]=File and URL opener Comment[te]=ఫైల్ మరియు URL తెరువునది -Comment[tg]=Кушодани файлҳо ва URL Comment[th]=ตัวเปิดแฟ้มและที่อยู่ URL Comment[tr]=Dosya ve Adres açıcı Comment[ug]=ھۆججەت ۋە URL ئاچقۇچ diff --git a/runners/places/plasma-runner-places.desktop b/runners/places/plasma-runner-places.desktop --- a/runners/places/plasma-runner-places.desktop +++ b/runners/places/plasma-runner-places.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Places Name[ar]=أماكن +Name[ast]=Llugares Name[be@latin]=Miescy Name[bg]=Места Name[bn]=স্থান @@ -81,7 +82,7 @@ Comment[ar]=افتح علامات الأجهزة والمجلدات Comment[bs]=Otvoreni uređaji i mape favorita Comment[ca]=Obre els punts dels dispositius i dels preferits -Comment[ca@valencia]=Obri els punts dels dispositius i dels preferits +Comment[ca@valencia]=Obri els marcadors dels dispositius i dels preferits Comment[cs]=Otevřít záložky zařízení a složek Comment[da]=Åbn enheder og mappebogmærker Comment[de]=Geräte und Lesezeichen für Ordner öffnen @@ -111,7 +112,7 @@ Comment[nl]=Apparaten- en mappenbladwijzers openen Comment[nn]=Opna einingar og mappebokmerke Comment[pa]=ਜੰਤਰ ਅਤੇ ਫੋਲਡਰ ਬੁੱਕਮਾਰਕ ਖੋਲ੍ਹੋ -Comment[pl]=Otwiera urządzenia i zakładki katalogów +Comment[pl]=Otwiera urządzenia i katalogi ze skrótów Comment[pt]=Abrir os favoritos dos dispositivos e pastas Comment[pt_BR]=Abre os favoritos dos dispositivos e pastas Comment[ro]=Deschide semne de carte pentru dispozitive și dosare diff --git a/runners/powerdevil/PowerDevilRunner.cpp b/runners/powerdevil/PowerDevilRunner.cpp --- a/runners/powerdevil/PowerDevilRunner.cpp +++ b/runners/powerdevil/PowerDevilRunner.cpp @@ -60,7 +60,7 @@ << i18nc("Note this is a KRunner keyword", "screen brightness") << i18nc("Note this is a KRunner keyword", "dim screen"); - foreach (const QString &command, commands) { + for (const QString &command : qAsConst(commands)) { if (command.length() < m_shortestCommand) { m_shortestCommand = command.length(); } @@ -124,7 +124,7 @@ bool PowerDevilRunner::parseQuery(const QString& query, const QList& rxList, QString& parameter) const { - foreach (const QRegExp& rx, rxList) { + for (const QRegExp& rx : rxList) { if (rx.exactMatch(query)) { parameter = rx.cap(1).trimmed(); return true; diff --git a/runners/powerdevil/plasma-runner-powerdevil.desktop b/runners/powerdevil/plasma-runner-powerdevil.desktop --- a/runners/powerdevil/plasma-runner-powerdevil.desktop +++ b/runners/powerdevil/plasma-runner-powerdevil.desktop @@ -4,7 +4,7 @@ Comment[be@latin]=Prostyja aperacyi dla kiravańnia enerhijaj Comment[bg]=Основни операции по захранването Comment[bs]=Osnovni postupci upravljanja napajanjem -Comment[ca]=Operacions bàsiques de gestió d'energia +Comment[ca]=Operacions bàsiques en la gestió d'energia Comment[ca@valencia]=Operacions bàsiques de gestió d'energia Comment[cs]=Základní úkony týkající se správy napájení Comment[csb]=Spòdlowé òperacejë sprôwiania mòcą @@ -36,7 +36,7 @@ Comment[kn]=ಮೂಲಭೂತ ವಿದ್ಯುಚ್ಛಕ್ತಿ ವ್ಯವಸ್ಥಾಪನಾ ಕಾರ್ಯಾಚರಣೆಗಳು Comment[ko]=기본 전원 관리 작업 Comment[ku]=Çalakiyên Rêveberiya Hêzê yê Bingehîn -Comment[lt]=Elementarios maitinimo valdymo operacijos +Comment[lt]=Pagrindinės maitinimo valdymo operacijos Comment[lv]=Pamata energokontroles darbības Comment[mk]=Основни операции за менаџмент на енергија Comment[ml]=അടിസ്ഥാനപരമായ പവര്‍ മാനേജ്മെന്റ് ബാക്കെന്‍ഡ് നടപടികള്‍ @@ -47,7 +47,7 @@ Comment[nn]=Grunnleggjande straumstyringsfunksjonar Comment[or]=ମୌଳିକ ଶକ୍ତି ପରିଚାଳନା ପ୍ରୟୋଗଗୁଡ଼ିକ Comment[pa]=ਬੇਸਿਕ ਪਾਵਰ ਮੈਨਿਜਮੈਂਟ ਓਪਰੇਸ਼ਨ -Comment[pl]=Podstawowe zarządzanie energią +Comment[pl]=Umożliwia zarządzanie energią Comment[pt]=Operações Básicas de Gestão da Energia Comment[pt_BR]=Operações básicas de gerenciamento de energia Comment[ro]=Operații de bază pentru gestiunea energiei @@ -61,7 +61,6 @@ Comment[sr@latin]=Osnovni postupci upravljanja napajanjem Comment[sv]=Grundläggande strömsparfunktioner Comment[ta]=Basic Power Management Operations -Comment[tg]=Служба управления питанием ноутбука Comment[th]=ปฏิบัติการจัดการพลังงานขั้นพื้นฐาน Comment[tr]=Temel Güç Yönetimi İşlemleri Comment[ug]=ئاساسىي توك مەنبە باشقۇرۇش مەشغۇلاتى @@ -73,6 +72,7 @@ Comment[zh_TW]=基本的電源管理操作 Icon=preferences-system-power-management Name=Power +Name[ast]=Enerxía Name[ca]=Energia Name[ca@valencia]=Energia Name[cs]=Energie @@ -94,6 +94,7 @@ Name[ja]=電源 Name[ko]=전원 Name[lt]=Maitinimas +Name[lv]=Enerģija Name[nl]=Energie Name[nn]=På / av Name[pa]=ਪਾਵਰ @@ -108,6 +109,7 @@ Name[sr@ijekavianlatin]=napajanje Name[sr@latin]=napajanje Name[sv]=Effekt +Name[tg]=Барқ Name[tr]=Güç Name[uk]=Живлення Name[x-test]=xxPowerxx diff --git a/runners/recentdocuments/plasma-runner-recentdocuments.desktop b/runners/recentdocuments/plasma-runner-recentdocuments.desktop --- a/runners/recentdocuments/plasma-runner-recentdocuments.desktop +++ b/runners/recentdocuments/plasma-runner-recentdocuments.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Recent Documents Name[ar]=المستندات الأخيرة +Name[ast]=Documentos d'apocayá Name[be]=Нядаўнія дакументы Name[be@latin]=Niadaŭnyja dakumenty Name[bg]=Последно използвани @@ -42,7 +43,7 @@ Name[kn]=ಇತ್ತೀಚಿನ ದಸ್ತಾವೇಜುಗಳು Name[ko]=최근 문서 Name[ku]=Pelgeyên Hatine Bikaranîn -Name[lt]=Neseni dokumentai +Name[lt]=Paskiausiai naudoti dokumentai Name[lv]=Nesenie dokumenti Name[mai]=हालक दस्ताबेज Name[mk]=Скорешни документи @@ -69,7 +70,7 @@ Name[sv]=Senaste dokument Name[ta]=Recent Documents Name[te]=ఇటీవలి పత్రములు -Name[tg]=Ҳуҷҷатҳои кушодашуда +Name[tg]=Ҳуҷҷатҳои охирин Name[th]=เอกสารที่เรียกใช้ไม่นานนี้ Name[tr]=Son Kullanılan Belgeler Name[ug]=يېقىنقى پۈتۈكلەر diff --git a/runners/recentdocuments/recentdocuments.cpp b/runners/recentdocuments/recentdocuments.cpp --- a/runners/recentdocuments/recentdocuments.cpp +++ b/runners/recentdocuments/recentdocuments.cpp @@ -78,7 +78,7 @@ // avoid duplicates QSet knownUrls; - foreach (const QString &document, m_recentdocuments) { + for (const QString &document : qAsConst(m_recentdocuments)) { if (!context.isValid()) { return; } diff --git a/runners/services/autotests/servicerunnertest.cpp b/runners/services/autotests/servicerunnertest.cpp --- a/runners/services/autotests/servicerunnertest.cpp +++ b/runners/services/autotests/servicerunnertest.cpp @@ -84,14 +84,14 @@ bool signalFound = false; for (auto match : context.matches()) { qDebug() << "matched" << match.text(); - if (!match.text().contains(QStringLiteral("ServiceRunnerTest"))) { + if (!match.text().contains(QLatin1String("ServiceRunnerTest"))) { continue; } - if (match.text() == QStringLiteral("Google Chrome ServiceRunnerTest")) { + if (match.text() == QLatin1String("Google Chrome ServiceRunnerTest")) { QCOMPARE(match.relevance(), 0.8); chromeFound = true; - } else if (match.text() == QStringLiteral("Signal ServiceRunnerTest")) { + } else if (match.text() == QLatin1String("Signal ServiceRunnerTest")) { // Rates lower because it doesn't have it in the name. QCOMPARE(match.relevance(), 0.7); signalFound = true; @@ -114,14 +114,14 @@ bool yakuakeFound = false; for (auto match : context.matches()) { qDebug() << "matched" << match.text(); - if (!match.text().contains(QStringLiteral("ServiceRunnerTest"))) { + if (!match.text().contains(QLatin1String("ServiceRunnerTest"))) { continue; } - if (match.text() == QStringLiteral("Konsole ServiceRunnerTest")) { + if (match.text() == QLatin1String("Konsole ServiceRunnerTest")) { QCOMPARE(match.relevance(), 0.99); konsoleFound = true; - } else if (match.text() == QStringLiteral("Yakuake ServiceRunnerTest")) { + } else if (match.text() == QLatin1String("Yakuake ServiceRunnerTest")) { // Rates lower because it doesn't have it in the name. QCOMPARE(match.relevance(), 0.59); yakuakeFound = true; @@ -148,10 +148,10 @@ bool foreignSystemSettingsFound = false; for (auto match : context.matches()) { qDebug() << "matched" << match.text(); - if (match.text() == QStringLiteral("System Settings ServiceRunnerTest")) { + if (match.text() == QLatin1String("System Settings ServiceRunnerTest")) { systemSettingsFound = true; } - if (match.text() == QStringLiteral("KDE System Settings ServiceRunnerTest")) { + if (match.text() == QLatin1String("KDE System Settings ServiceRunnerTest")) { foreignSystemSettingsFound = true; } } diff --git a/runners/services/plasma-runner-services.desktop b/runners/services/plasma-runner-services.desktop --- a/runners/services/plasma-runner-services.desktop +++ b/runners/services/plasma-runner-services.desktop @@ -3,6 +3,7 @@ Name[af]=Programme Name[ar]=التطبيقات Name[as]=অনুপ্ৰয়োগ +Name[ast]=Aplicaciones Name[be]=Праграмы Name[be@latin]=Aplikacyi Name[bg]=Програми @@ -28,7 +29,7 @@ Name[fr]=Applications Name[fy]=Programma's Name[ga]=Feidhmchláir -Name[gl]=Aplicativos +Name[gl]=Aplicacións Name[gu]=કાર્યક્રમો Name[he]=תוכניות Name[hi]=अनुप्रयोग @@ -96,7 +97,7 @@ Comment[bg]=Търсене на програми, панели и услуги Comment[bs]=Tražite programe, kontrolne panele i servise Comment[ca]=Cerca aplicacions, plafons de control i serveis -Comment[ca@valencia]=Cerca aplicacions, plafons de control i serveis +Comment[ca@valencia]=Busca aplicacions, plafons de control i serveis Comment[cs]=Najít aplikace, ovládací panely a služby Comment[da]=Find programmer, kontrolpaneler og tjenester Comment[de]=Auffinden von Anwendungen, Einrichtungsprogrammen und Diensten @@ -110,7 +111,7 @@ Comment[fr]=Trouver des applications, des tableaux de bords et des services Comment[fy]=Sykje om applikaasjes, konfiguraasjemodules en tsjinsten Comment[ga]=Aimsigh feidhmchláir, painéil rialaithe agus seirbhísí -Comment[gl]=Atopar aplicativos, paneis de control e servizos. +Comment[gl]=Atopar aplicacións, paneis de control e servizos. Comment[gu]=કાર્યક્રમો, નિયંત્રણ પેનલો અને સેવાઓ શોધો Comment[he]=מצא קבצים הגדרות ושרותים Comment[hi]=अनुप्रयोग, नियंत्रण पटल तथा सेवाएँ ढूंढें @@ -127,7 +128,7 @@ Comment[kn]=ಅನ್ವಯಗಳು, ನಿಯಂತ್ರಣಾ ಪುಟೀಪುಗಳು (ಕಂಟ್ರೋಲ್ ಪಾನಲ್ಸ್) ಹಾಗೂ ಸೇವೆಗಳನ್ನು ಹುಡುಕು Comment[ko]=프로그램; 제어판; 서비스 찾기 Comment[ku]=Sepanan, panelên kontrolê û servîsan bibîne -Comment[lt]=Rasti programas, valdymo pultus ir tarnybas +Comment[lt]=Rasti programas, valdyti skydelius ir tarnybas Comment[lv]=Meklēt lietotnes, kontroles paneļus un servisus Comment[mk]=Пронајдете апликации, контролни панели и сервиси Comment[ml]=പ്രയോഗങ്ങള്‍, നിയന്ത്രണപാനലുകള്‍, സേവനങ്ങള്‍ എന്നിവ കണ്ടുപിടിയ്ക്കുക @@ -153,7 +154,6 @@ Comment[sv]=Sök efter program, kontrollpaneler och tjänster Comment[ta]=Find applications, control panels and services Comment[te]=అనువర్తనములను, కంట్రోల్ ప్యానల్‍స్ ను మరియు సేవలను కనుగొనుము -Comment[tg]=Ҷустуҷӯи барномаҳо ва хизматҳо Comment[th]=ค้นหาโปรแกรม, ถาดควบคุม และบริการ Comment[tr]=Uygulamaları, kontrol panellerini ve servisleri bul Comment[ug]=پروگرامما، تىزگىن تاختا ۋە مۇلازىمەت ئىزدە diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp --- a/runners/services/servicerunner.cpp +++ b/runners/services/servicerunner.cpp @@ -134,7 +134,7 @@ return relevanceIncrement; } - QString generateQuery(QVector &strList) + QString generateQuery(const QVector &strList) { QString keywordTemplate = QStringLiteral("exist Keywords"); QString genericNameTemplate = QStringLiteral("exist GenericName"); @@ -147,7 +147,7 @@ // * a substring of the Name field // Note that before asking for the content of e.g. Keywords and GenericName we need to ask if // they exist to prevent a tree evaluation error if they are not defined. - foreach (QStringRef str, strList) + for (const QStringRef &str : strList) { keywordTemplate += QStringLiteral(" and '%1' ~subin Keywords").arg(str.toString()); genericNameTemplate += QStringLiteral(" and '%1' ~~ GenericName").arg(str.toString()); @@ -187,16 +187,16 @@ } // Search for applications which are executable and case-insensitively match the search term - // See http://techbase.kde.org/Development/Tutorials/Services/Traders#The_KTrader_Query_Language + // See https://techbase.kde.org/Development/Tutorials/Services/Traders#The_KTrader_Query_Language // if the following is unclear to you. query = QStringLiteral("exist Exec and ('%1' =~ Name)").arg(term); - KService::List services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), query); + const KService::List services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), query); if (services.isEmpty()) { return; } - foreach (const KService::Ptr &service, services) { + for (const KService::Ptr &service : services) { qCDebug(RUNNER_SERVICES) << service->name() << "is an exact match!" << service->storageId() << service->exec(); if (disqualify(service)) { continue; @@ -226,7 +226,7 @@ services += KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), query); qCDebug(RUNNER_SERVICES) << "got " << services.count() << " services from " << query; - foreach (const KService::Ptr &service, services) { + for (const KService::Ptr &service : qAsConst(services)) { if (disqualify(service)) { continue; } @@ -278,14 +278,14 @@ } } - if (service->categories().contains(QStringLiteral("KDE")) || service->serviceTypes().contains(QStringLiteral("KCModule"))) { + if (service->categories().contains(QLatin1String("KDE")) || service->serviceTypes().contains(QLatin1String("KCModule"))) { qCDebug(RUNNER_SERVICES) << "found a kde thing" << id << match.subtext() << relevance; relevance += .09; } qCDebug(RUNNER_SERVICES) << service->name() << "is this relevant:" << relevance; match.setRelevance(relevance); - if (service->serviceTypes().contains(QStringLiteral("KCModule"))) { + if (service->serviceTypes().contains(QLatin1String("KCModule"))) { match.setMatchCategory(i18n("System Settings")); } matches << match; @@ -296,9 +296,9 @@ { //search for applications whose categories contains the query query = QStringLiteral("exist Exec and (exist Categories and '%1' ~subin Categories)").arg(term); - auto services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), query); + const auto services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), query); - foreach (const KService::Ptr &service, services) { + for (const KService::Ptr &service : services) { qCDebug(RUNNER_SERVICES) << service->name() << "is an exact match!" << service->storageId() << service->exec(); if (disqualify(service)) { continue; @@ -309,7 +309,7 @@ setupMatch(service, match); qreal relevance = 0.6; - if (service->categories().contains(QStringLiteral("X-KDE-More")) || + if (service->categories().contains(QLatin1String("X-KDE-More")) || !service->showInCurrentDesktop()) { relevance = 0.5; } @@ -330,14 +330,14 @@ } query = QStringLiteral("exist Actions"); // doesn't work - auto services = KServiceTypeTrader::self()->query(QStringLiteral("Application"));//, query); + const auto services = KServiceTypeTrader::self()->query(QStringLiteral("Application"));//, query); - foreach (const KService::Ptr &service, services) { + for (const KService::Ptr &service : services) { if (service->noDisplay()) { continue; } - foreach (const KServiceAction &action, service->actions()) { + for (const KServiceAction &action : service->actions()) { if (action.text().isEmpty() || action.exec().isEmpty() || hasSeen(action)) { continue; } @@ -451,7 +451,7 @@ QString path = service->entryPath(); if (!QDir::isAbsolutePath(path)) { - path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/") + path); + path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + path); } if (path.isEmpty()) { diff --git a/runners/sessions/plasma-runner-sessions.desktop b/runners/sessions/plasma-runner-sessions.desktop --- a/runners/sessions/plasma-runner-sessions.desktop +++ b/runners/sessions/plasma-runner-sessions.desktop @@ -39,7 +39,7 @@ Name[kn]=ಗಣಕತೆರೆ ಅಧಿವೇಶನಗಳು (ಸೆಶನ್) Name[ko]=데스크톱 세션 Name[ku]=Danişînên Sermaseyê -Name[lt]=Darbalaukio sesijos +Name[lt]=Darbalaukio seansai Name[lv]=Darbvirsmas sesijas Name[mk]=Работни сесии Name[ml]=പണിയിടത്തിന്റെ പ്രവര്‍ത്തനവേളകള്‍ @@ -65,7 +65,7 @@ Name[sv]=Skrivbordssessioner Name[ta]=Desktop Sessions Name[te]=డెస్‍క్ టాప్ భాగములు -Name[tg]=Амалҳои мизи корӣ +Name[tg]=Ҷаласаҳои мизи корӣ Name[th]=วาระงานของพื้นที่ทำงาน Name[tr]=Masaüstü Oturumları Name[ug]=ئۈستەلئۈستى ئەڭگىمە @@ -138,7 +138,6 @@ Comment[sv]=Snabbt användarbyte Comment[ta]=Fast user switching Comment[te]=వేగవంతమైన వినియోగదారి మార్పు -Comment[tg]=Мубодилаи фаврии корбар Comment[th]=สลับผู้ใช้งานด่วน Comment[tr]=Hızlı kullanıcı değiştirme Comment[ug]=ئىشلەتكۈچىنى تېز ئالماشتۇرۇش diff --git a/runners/sessions/sessionrunner.cpp b/runners/sessions/sessionrunner.cpp --- a/runners/sessions/sessionrunner.cpp +++ b/runners/sessions/sessionrunner.cpp @@ -171,7 +171,7 @@ SessList sessions; dm.localSessions(sessions); - foreach (const SessEnt& session, sessions) { + for (const SessEnt& session : qAsConst(sessions)) { if (!session.vt || session.self) { continue; } diff --git a/runners/shell/plasma-runner-shell.desktop b/runners/shell/plasma-runner-shell.desktop --- a/runners/shell/plasma-runner-shell.desktop +++ b/runners/shell/plasma-runner-shell.desktop @@ -22,7 +22,7 @@ Name[fr]=Ligne de commandes Name[fy]=Kommandorigel Name[ga]=Líne na nOrduithe -Name[gl]=Liña de ordes +Name[gl]=Execución de ordes Name[gu]=આદેશ જગ્યા Name[he]=שורת הפקודה Name[hi]=कमांड लाइन @@ -39,7 +39,7 @@ Name[kn]=ಆದೇಶ ತೆರೆ Name[ko]=명령줄 Name[ku]=Rêzika Fermanê -Name[lt]=Komandinė eilutė +Name[lt]=Komandų eilutė Name[lv]=Komandrinda Name[mai]=कमांड लाइन Name[mk]=Командна линија @@ -67,7 +67,7 @@ Name[sv]=Kommandorad Name[ta]=Command Line Name[te]=ఆదేశ వరుస -Name[tg]=Иҷрои фармон +Name[tg]=Сатри фармонҳо Name[th]=บรรทัดคำสั่ง Name[tr]=Komut Satırı Name[ug]=بۇيرۇق قۇرى @@ -79,6 +79,7 @@ Name[zh_TW]=命令列 Comment=Executes shell commands Comment[ar]=نفّذ أوامر الصَدفة +Comment[ast]=Executa comandos de la shell Comment[be@latin]=Vykanańnie zahadaŭ abałonki. Comment[bg]=Изпълнение на команди на обвивката Comment[bn]=শেল কমাণ্ড চালায় @@ -99,7 +100,7 @@ Comment[fr]=Exécute des commandes shell Comment[fy]=Flueskommando's útfiere Comment[ga]=Rith orduithe blaoisce -Comment[gl]=Executa ordes do intérprete de ordes +Comment[gl]=Executa ordes da shell Comment[gu]=શૅલ આદેશ ચલાવો Comment[he]=הרץ פקודת Shell Comment[hi]=शेल कमांड्स चलाता है @@ -142,7 +143,6 @@ Comment[sv]=Kör skalkommandon Comment[ta]=Executes shell commands Comment[te]=షెల్ ఆదేశాలను నిర్వర్తిస్తుంది -Comment[tg]=Выполняет команды оболочки Comment[th]=ประมวลผลคำสั่งของเชลล์ Comment[tr]=Kabuk komutlarını çalıştırır Comment[ug]=shell بۇيرۇقنى ئىجرا قىلىدۇ diff --git a/runners/webshortcuts/plasma-runner-webshortcuts.desktop b/runners/webshortcuts/plasma-runner-webshortcuts.desktop --- a/runners/webshortcuts/plasma-runner-webshortcuts.desktop +++ b/runners/webshortcuts/plasma-runner-webshortcuts.desktop @@ -2,6 +2,7 @@ # ctxt: plasma runner Name=Web Shortcuts Name[ar]=اختصارات الوِب +Name[ast]=Atayos web Name[bg]=Уеб отметки Name[bn]=ওয়েব শর্টকাট Name[bs]=Veb prečice @@ -37,7 +38,7 @@ Name[km]=ផ្លូវកាត់​បណ្ដាញ​ Name[kn]=ಜಾಲ ಶೀಘ್ರಮಾರ್ಗಗಳು (ಶಾರ್ಟ್ ಕಟ್) Name[ko]=웹 바로 가기 -Name[lt]=Žiniatinklio trumpės +Name[lt]=Saityno nuorodos Name[lv]=Ātrās meklēšanas īsceļi Name[mk]=Интернет-кратенки Name[ml]=വെബിലെ കുറക്കുവഴികള്‍ @@ -60,7 +61,6 @@ Name[sr@ijekavianlatin]=veb prečice Name[sr@latin]=veb prečice Name[sv]=Webbgenvägar -Name[tg]=Тугмаҳои тез Name[th]=ทางลัดถึงเว็บ Name[tr]=Web Kısayolları Name[ug]=تور تېزلەتمىسى @@ -71,9 +71,10 @@ Name[zh_TW]=網頁捷徑 Comment=Allows user to use Konqueror's web shortcuts Comment[ar]=يسمح للمستخدِم باستخدام اختصارات كنكر للوِب +Comment[ast]=Permite al usuariu qu'use los atayos web de Konqueror Comment[bs]=Dozvoli koristenje Koquerorovih web prečica Comment[ca]=Permet que l'usuari utilitzi les dreceres web del Konqueror -Comment[ca@valencia]=Permet que l'usuari utilitze les dreceres web del Konqueror +Comment[ca@valencia]=Permet que l'usuari utilitze les dreceres web de Konqueror Comment[cs]=Umožňuje používat webové zkratky Konqueroru Comment[da]=Lader brugeren anvende Konquerors webgenveje Comment[de]=Ermöglicht die Verwendung von Web-Kürzeln in Konqueror @@ -97,7 +98,7 @@ Comment[kk]=Konqueror-дың Веб қысқармаларын пайдалануға мүмкіндік беру Comment[km]=អនុញ្ញាត​ឲ្យ​អ្នកប្រើ​ ប្រើ​ផ្លូវកាត់​បណ្ដាញ​របស់ Konqueror Comment[ko]=Konqueror 웹 바로 가기 사용 허용 -Comment[lt]=Leidžia naudotojui naudoti Konqueror žiniatinklio nuorodas +Comment[lt]=Leidžia naudotojui naudoti Konqueror saityno nuorodas Comment[mr]=कॉन्कररचे वेब शार्टकट वापरण्यास वापरकर्त्यास परवानगी देतो Comment[nb]=Lar brukeren benytte nettsnarveier i Konqueror Comment[nds]=Lett den Bruker Konqueror sien Söökafkörten bruken diff --git a/runners/windowedwidgets/plasma-runner-windowedwidgets.desktop b/runners/windowedwidgets/plasma-runner-windowedwidgets.desktop --- a/runners/windowedwidgets/plasma-runner-windowedwidgets.desktop +++ b/runners/windowedwidgets/plasma-runner-windowedwidgets.desktop @@ -1,10 +1,11 @@ [Desktop Entry] Name=Windowed widgets Name[ar]=الودجات ذات النوافذ +Name[ast]=Widgets en ventana Name[bg]=Джаджи като прозорци Name[bs]=Grafičke kontrole s prozorom Name[ca]=Estris en finestres -Name[ca@valencia]=Estris en finestres +Name[ca@valencia]=Ginys («widgets») en finestres Name[cs]=Widgety v okně Name[da]=Widgets som vinduer Name[de]=Eigenständige Miniprogramme @@ -22,15 +23,15 @@ Name[hr]=Widgeti u prozorima Name[hu]=Önálló widgetek Name[ia]=Widgets (elemento graphic) in le fenestra -Name[id]=Widget per-window-an +Name[id]=Windowed widgets Name[is]=Græjur í gluggum Name[it]=Oggetti a finestra Name[ja]=ウィンドウ化されたウィジェット Name[kk]=Терезелерлі виджеттер Name[km]=ធាតុក្រាហ្វិក​​វីនដូ Name[kn]=ವಿಂಡೋವ್ಡ್ ನಿಯಂತ್ರಣಾ ಸಂಪರ್ಕತಟಗಳು (ವಿಡ್ಗೆಟ್) Name[ko]=창에 들어있는 위젯 -Name[lt]=Langų valdikliai +Name[lt]=Languoti valdikliai Name[lv]=Logoti sīkrīki Name[mr]=चौकटीतील विजेट्स Name[nb]=Skjermelementer i vinduer @@ -66,7 +67,7 @@ Comment[bg]=Търсене на джаджи, които могат да се използват като самостоятелни прозорци Comment[bs]=Nađite plazma grafičke kontrole koje mogu da rade kao samostalni prozori Comment[ca]=Cerca estris del Plasma que es poden executar en finestres autònomes -Comment[ca@valencia]=Cerca estris del Plasma que es poden executar en finestres autònomes +Comment[ca@valencia]=Busca ginys («widgets») de Plasma que es puguen executar en finestres autònomes Comment[cs]=Najít Plasma widgety, které mohou být spuštěny ve vlastních oknech Comment[da]=Find Plasma-widgets der kan køres som selvstændige vinduer Comment[de]=Findet Plasma-Miniprogramme, die als eigenständige Anwendungen gestartet werden können @@ -83,14 +84,14 @@ Comment[hr]=Pronađi Plasma widgete koji se mogu pokrenuti kao zasebni prozori Comment[hu]=Megkeresi az önálló alkalmazásként futtatható Plasma-elemeket Comment[ia]=Trova widgets (elementos graphic) de plasma que on pote executar como fenestras sole -Comment[id]=Temukan widget Plasma yang dapat dijalankan sebagai window-window mandiri +Comment[id]=Temukan widget Plasma yang bisa berjalan sebagai window mandiri Comment[is]=Finnur Plasmagræjur sem hægt er að keyra sem sjálfstæð forrit Comment[it]=Trova i plasmoidi che possono essere eseguiti come finestre a sé Comment[ja]=スタンドアローンのウィンドウとして起動できる Plasma ウィジェットを検索します Comment[kk]=Бөлек терезелі Plasma виджеттерін табу Comment[km]=រក​ធាតុក្រាហ្វិក​ប្លាស្មា​ ដែល​អាច​ត្រូវ​បាន​​រត់​ជា​វីនដូ​តែ​ឯង Comment[ko]=창으로 실행될 수 있는 위젯을 찾습니다 -Comment[lt]=Rasti plasmos elementus, kurie gali veikti kaip atskiri langai +Comment[lt]=Rasti Plasma valdiklius, kurie gali veikti kaip atskiri langai Comment[lv]=Meklē Plasma sīkrīkus, ko var darbināt kā patstāvīgus logus Comment[mr]=चौकटीत चालण्याजोगी प्लाज्मा विजेट्स शोधा Comment[nb]=Finn Plasmaelementer som kan kjøres som frittstående programmer diff --git a/runners/windowedwidgets/windowedwidgetsrunner.cpp b/runners/windowedwidgets/windowedwidgetsrunner.cpp --- a/runners/windowedwidgets/windowedwidgetsrunner.cpp +++ b/runners/windowedwidgets/windowedwidgetsrunner.cpp @@ -57,10 +57,10 @@ return; } + QList matches; - QList matches; - - foreach (const KPluginMetaData &md, Plasma::PluginLoader::self()->listAppletMetaData(QString())) { + const auto &listMetadata = Plasma::PluginLoader::self()->listAppletMetaData(QString()); + for (const KPluginMetaData &md : listMetadata) { if (!md.isValid()) { continue; } diff --git a/runners/windows/plasma-runner-windows.desktop b/runners/windows/plasma-runner-windows.desktop --- a/runners/windows/plasma-runner-windows.desktop +++ b/runners/windows/plasma-runner-windows.desktop @@ -2,6 +2,7 @@ # ctxt: plasma runner Name=Windows Name[ar]=النوافذ +Name[ast]=Ventanes Name[bg]=Прозорци Name[bn]=উইণ্ডো Name[bs]=Prozori @@ -29,7 +30,7 @@ Name[hr]=Prozori Name[hu]=Ablakok Name[ia]=Fenestras -Name[id]=Window +Name[id]=Windows Name[is]=Gluggar Name[it]=Finestre Name[ja]=ウィンドウ @@ -60,7 +61,6 @@ Name[sr@ijekavianlatin]=prozori Name[sr@latin]=prozori Name[sv]=Fönster -Name[tg]=Windows Name[th]=หน้าต่างต่าง ๆ Name[tr]=Pencereler Name[ug]=كۆزنەكلەر @@ -73,7 +73,7 @@ Comment[ar]=اسرد النوافذ وأسطح المكتب وبدّل بينها Comment[bg]=Показване и превключване на прозорци и работни плотове Comment[bs]=Nabrajanje i prebacivanje između prozora i površî -Comment[ca]=Llista finestres i escriptoris i canvia entre ells +Comment[ca]=Llista finestres i escriptoris, i canvia entre ells Comment[ca@valencia]=Llista finestres i escriptoris i canvia entre ells Comment[cs]=Seznam oken a ploch k přepínání Comment[da]=Oplist vinduer og skriveborde og skift mellem dem @@ -101,7 +101,7 @@ Comment[km]=រាយ​​បង្អួច និង​ផ្ទៃតុ​ ហើយ​ប្ដូរ​ពួកវា Comment[kn]=ವಿಂಡೊ ಹಾಗು ಗಣಕತೆರೆಗಳನ್ನು ಪಟ್ಟಿಮಾಡು ಹಾಗು ಅವುಗಳನ್ನು ಬದಲಾಯಿಸು Comment[ko]=창 및 데스크톱 목록을 보여 주고 전환합니다 -Comment[lt]=Išvardija langus ir darbalaukius su galimybe į juos pereiti +Comment[lt]=Išvardyti langus ir darbalaukius bei perjungti į juos Comment[lv]=Parāda logus un darbvirsmas un pārslēdz tos Comment[mk]=Приказ на прозорци и раб. површини и преминување меѓу нив Comment[ml]=ജാലകങ്ങളും പണിയിടങ്ങളും പട്ടികയായി കണ്ടു് പരസ്പരം മാറുക @@ -124,7 +124,6 @@ Comment[sr@ijekavianlatin]=Nabrajanje i prebacivanje između prozora i površî Comment[sr@latin]=Nabrajanje i prebacivanje između prozora i površî Comment[sv]=Lista fönster och skrivbord, och byt mellan dem -Comment[tg]=Намоиши рӯйхати тирезаҳо, мизҳои корӣ ва панелҳо Comment[th]=รายการหน้าต่างและพื้นที่ทำงานต่าง ๆ ที่สามารถสลับไปใช้งานได้ Comment[tr]=Pencereleri ve masaüstlerini listele ve seç Comment[ug]=ھەممە كۆزنەكلەر ۋە ئۈستەلئۈستى تىزىمىنى كۆرسىتىپ، ئۇلارنى ئالماشتۇرىدۇ diff --git a/runners/windows/windowsrunner.h b/runners/windows/windowsrunner.h --- a/runners/windows/windowsrunner.h +++ b/runners/windows/windowsrunner.h @@ -21,6 +21,8 @@ #include +#include + class KWindowInfo; class WindowsRunner : public Plasma::AbstractRunner diff --git a/runners/windows/windowsrunner.cpp b/runners/windows/windowsrunner.cpp --- a/runners/windows/windowsrunner.cpp +++ b/runners/windows/windowsrunner.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -74,7 +75,8 @@ return; } - foreach (const WId w, KWindowSystem::windows()) { + const auto windows = KWindowSystem::windows(); + for (const WId &w : windows) { KWindowInfo info(w, NET::WMWindowType | NET::WMDesktop | NET::WMState | NET::XAWMState | NET::WMName, @@ -174,12 +176,12 @@ // keyword match: when term starts with "window" we list all windows // the list can be restricted to windows matching a given name, class, role or desktop if (term.startsWith(i18nc("Note this is a KRunner keyword", "window") , Qt::CaseInsensitive)) { - const QStringList keywords = term.split(QStringLiteral(" ")); + const QStringList keywords = term.split(QLatin1Char(' ')); QString windowName; QString windowClass; QString windowRole; int desktop = -1; - foreach (const QString& keyword, keywords) { + for (const QString& keyword : keywords) { if (keyword.endsWith(QLatin1Char('='))) { continue; } @@ -214,7 +216,7 @@ if (!KWindowSystem::hasWId(w)) { continue; } - if (!windowName.isEmpty() && !info.name().contains(windowName, Qt::CaseInsensitive)) { + if (!windowName.isEmpty() && !info.name().startsWith(windowName, Qt::CaseInsensitive)) { continue; } if (!windowClass.isEmpty() && !windowClassCompare.contains(windowClass, Qt::CaseInsensitive)) { @@ -252,7 +254,7 @@ bool desktopAdded = false; // check for desktop keyword if (term.startsWith(i18nc("Note this is a KRunner keyword", "desktop") , Qt::CaseInsensitive)) { - const QStringList parts = term.split(QStringLiteral(" ")); + const QStringList parts = term.split(QLatin1Char(' ')); if (parts.size() == 1) { // only keyword - list all desktops for (int i=1; i<=KWindowSystem::numberOfDesktops(); i++) { @@ -295,7 +297,7 @@ } // check for matching desktops by name - foreach (const QString& desktopName, m_desktopNames) { + for (const QString& desktopName : qAsConst(m_desktopNames)) { int desktop = m_desktopNames.indexOf(desktopName) +1; if (desktopName.contains(term, Qt::CaseInsensitive)) { // desktop name matches - offer switch to @@ -332,12 +334,16 @@ return; } - const QStringList parts = match.data().toString().split(QStringLiteral("_")); + const QStringList parts = match.data().toString().split(QLatin1Char('_')); WindowAction action = WindowAction(parts[0].toInt()); WId w(parts[1].toULong()); - //this is needed since KWindowInfo() doesn't exist, m_windows[w] doesn't work - QHash::iterator i = m_windows.find(w); - KWindowInfo info = i.value(); + + KWindowInfo info(w, NET::WMWindowType | NET::WMDesktop | NET::WMState | NET::XAWMState | NET::WMName, + NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2AllowedActions); + if (!info.valid()) { + return; + } + switch (action) { case ActivateAction: KWindowSystem::forceActiveWindow(w); diff --git a/sddm-theme/Main.qml b/sddm-theme/Main.qml --- a/sddm-theme/Main.qml +++ b/sddm-theme/Main.qml @@ -37,6 +37,7 @@ readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software colorGroup: PlasmaCore.Theme.ComplementaryColorGroup + readonly property bool lightBackground: Math.max(PlasmaCore.ColorScope.backgroundColor.r, PlasmaCore.ColorScope.backgroundColor.g, PlasmaCore.ColorScope.backgroundColor.b) > 0.5 width: 1600 height: 900 @@ -130,7 +131,7 @@ radius: 6 samples: 14 spread: 0.3 - color: "black" // matches Breeze window decoration and desktopcontainment + color: root.lightBackground ? PlasmaCore.ColorScope.backgroundColor : "black" // black matches Breeze window decoration and desktopcontainment Behavior on opacity { OpacityAnimator { duration: 1000 @@ -147,7 +148,6 @@ anchors.horizontalCenter: parent.horizontalCenter } - StackView { id: mainStack anchors { @@ -182,6 +182,8 @@ if ( userListModel.count === 0 ) return false + if ( userListModel.hasOwnProperty("containsAllUsers") && !userListModel.containsAllUsers ) return false + return userListModel.count <= userListModel.disableAvatarsThreshold && (userList.y + mainStack.y) > 0 } @@ -409,6 +411,46 @@ } } + Image { + id: logo + visible: config.showlogo == "shown" + source: config.logo + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: footer.top + anchors.bottomMargin: units.largeSpacing + asynchronous: true + sourceSize.height: height + opacity: loginScreenRoot.uiVisible ? 0 : 1 + fillMode: Image.PreserveAspectFit + height: Math.round(units.gridUnit * 3.5) + Behavior on opacity { + OpacityAnimator { + duration: units.longDuration + easing.type: Easing.InOutQuad + } + } + } + + DropShadow { + id: logoShadow + anchors.fill: logo + source: logo + visible: !softwareRendering && config.showlogo == "shown" + horizontalOffset: 1 + verticalOffset: 1 + radius: 6 + samples: 14 + spread: 0.3 + color: "black" // matches Breeze window decoration and desktopcontainment + opacity: loginScreenRoot.uiVisible ? 0 : 1 + Behavior on opacity { + OpacityAnimator { + duration: units.longDuration + easing.type: Easing.InOutQuad + } + } + } + //Footer RowLayout { id: footer diff --git a/sddm-theme/default-logo.svg b/sddm-theme/default-logo.svg new file mode 100644 --- /dev/null +++ b/sddm-theme/default-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/sddm-theme/dummydata/config.qml b/sddm-theme/dummydata/config.qml --- a/sddm-theme/dummydata/config.qml +++ b/sddm-theme/dummydata/config.qml @@ -4,4 +4,5 @@ property string type: "color" property color color: "#1d99f3" // Plasma blue property string background: "" + property int fontSize: 12 } diff --git a/sddm-theme/dummydata/screenModel.qml b/sddm-theme/dummydata/screenModel.qml --- a/sddm-theme/dummydata/screenModel.qml +++ b/sddm-theme/dummydata/screenModel.qml @@ -9,7 +9,7 @@ //Creating multiple ListElement objects doesn't work, because a ListElement can only take numbers, //strings, booleans or enums, but we need a rect (it stores the screen size/position). //We can insert rects into the ListModel by using this workaround - //It's taken from: http://stackoverflow.com/questions/20537417/add-statically-object-to-listmodel + //It's taken from: https://stackoverflow.com/questions/20537417/add-statically-object-to-listmodel //We create two monitors here, the left one 800x600 and the right one 800x400 pixels //To disable a screen delete one of the "append" function calls diff --git a/sddm-theme/metadata.desktop b/sddm-theme/metadata.desktop --- a/sddm-theme/metadata.desktop +++ b/sddm-theme/metadata.desktop @@ -1,6 +1,7 @@ [SddmGreeterTheme] Name=Breeze Name[ar]=نسيم +Name[ast]=Breeze Name[bs]=Breeze Name[ca]=Brisa Name[ca@valencia]=Brisa @@ -38,32 +39,40 @@ Name[sr@ijekavianlatin]=Povetarac Name[sr@latin]=Povetarac Name[sv]=Breeze +Name[tg]=Насим Name[tr]=Breeze Name[uk]=Breeze Name[x-test]=xxBreezexx Name[zh_CN]=微风 Name[zh_TW]=微風 Description=Breeze +Description[ast]=Breeze Description[ca]=Brisa Description[ca@valencia]=Brisa +Description[cs]=Breeze Description[da]=Breeze +Description[de]=Breeze Description[en_GB]=Breeze Description[es]=Brisa +Description[et]=Breeze Description[eu]=Breeze Description[fi]=Breeze Description[fr]=Breeze Description[gl]=Breeze +Description[hu]=Breeze Description[id]=Breeze Description[it]=Brezza Description[ko]=Breeze +Description[lt]=Breeze Description[nl]=Breeze Description[nn]=Breeze Description[pl]=Bryza Description[pt]=Brisa Description[pt_BR]=Breeze Description[ru]=Breeze Description[sk]=Vánok Description[sv]=Breeze +Description[tg]=Насим Description[uk]=Breeze Description[x-test]=xxBreezexx Description[zh_CN]=微风 diff --git a/sddm-theme/theme.conf.cmake b/sddm-theme/theme.conf.cmake --- a/sddm-theme/theme.conf.cmake +++ b/sddm-theme/theme.conf.cmake @@ -1,5 +1,8 @@ [General] +showlogo=hidden +logo=${KDE_INSTALL_FULL_DATADIR}/sddm/themes/breeze/default-logo.svg type=image color=#1d99f3 fontSize=10 background=${CMAKE_INSTALL_PREFIX}/${WALLPAPER_INSTALL_DIR}/Next/contents/images/5120x2880.png +needsFullUserModel=false diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -33,10 +33,10 @@ panelconfigview.cpp panelshadows.cpp shellcorona.cpp - shellmanager.cpp standaloneappcorona osd.cpp coronatesthelper.cpp + strutmanager.cpp debug.cpp screenpool.cpp softwarerendernotifier.cpp @@ -61,7 +61,6 @@ KF5::Solid KF5::Declarative KF5::I18n - KF5::IconThemes KF5::Activities KF5::GlobalAccel KF5::CoreAddons @@ -73,17 +72,24 @@ KF5::Notifications PW::KWorkspace ) +if (TARGET KUserFeedbackCore) + target_link_libraries(plasmashell KUserFeedbackCore) + target_compile_definitions(plasmashell PRIVATE -DWITH_KUSERFEEDBACKCORE) +endif() + target_include_directories(plasmashell PRIVATE "${CMAKE_BINARY_DIR}") target_compile_definitions(plasmashell PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") if(HAVE_X11) target_link_libraries(plasmashell ${X11_LIBRARIES} ${XCB_LIBRARIES} ) target_link_libraries(plasmashell Qt5::X11Extras) endif() +configure_file(org.kde.plasmashell.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.desktop @ONLY) + install(TARGETS plasmashell ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) -install(FILES org.kde.plasmashell.desktop DESTINATION ${KDE_INSTALL_APPDIR}) -install(FILES org.kde.plasmashell.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.desktop DESTINATION ${KDE_INSTALL_APPDIR}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) install( FILES dbus/org.kde.PlasmaShell.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install(FILES diff --git a/shell/autotests/desktopview.h b/shell/autotests/desktopview.h --- a/shell/autotests/desktopview.h +++ b/shell/autotests/desktopview.h @@ -35,7 +35,7 @@ ~DesktopView() override; /*This is different from screen() as is always there, even if the window is - temporarly outside the screen or if is hidden: only plasmashell will ever + temporarily outside the screen or if is hidden: only plasmashell will ever change this property, unlike QWindow::screen()*/ void setScreenToFollow(QScreen *screen); QScreen *screenToFollow() const; diff --git a/shell/currentcontainmentactionsmodel.h b/shell/currentcontainmentactionsmodel.h --- a/shell/currentcontainmentactionsmodel.h +++ b/shell/currentcontainmentactionsmodel.h @@ -45,7 +45,7 @@ HasConfigurationInterfaceRole }; - explicit CurrentContainmentActionsModel(Plasma::Containment *cotainment, QObject *parent = nullptr); + explicit CurrentContainmentActionsModel(Plasma::Containment *containment, QObject *parent = nullptr); ~CurrentContainmentActionsModel() override; QHash roleNames() const override; diff --git a/shell/currentcontainmentactionsmodel.cpp b/shell/currentcontainmentactionsmodel.cpp --- a/shell/currentcontainmentactionsmodel.cpp +++ b/shell/currentcontainmentactionsmodel.cpp @@ -39,15 +39,15 @@ #include -CurrentContainmentActionsModel::CurrentContainmentActionsModel(Plasma::Containment *cotainment, QObject *parent) +CurrentContainmentActionsModel::CurrentContainmentActionsModel(Plasma::Containment *containment, QObject *parent) : QStandardItemModel(parent), - m_containment(cotainment), + m_containment(containment), m_tempConfigParent(QString(), KConfig::SimpleConfig) { m_baseCfg = KConfigGroup(m_containment->corona()->config(), "ActionPlugins"); m_baseCfg = KConfigGroup(&m_baseCfg, QString::number(m_containment->containmentType())); - QHash actions = cotainment->containmentActions(); + QHash actions = containment->containmentActions(); QHashIterator i(actions); while (i.hasNext()) { @@ -223,7 +223,7 @@ connect(buttons, &QDialogButtonBox::rejected, configDlg, &QDialog::reject); QObject::connect(configDlg, &QDialog::accepted, pluginInstance, - [configDlg, pluginInstance] () { + [ pluginInstance] () { pluginInstance->configurationAccepted(); }); diff --git a/shell/dbus/org.kde.PlasmaShell.xml b/shell/dbus/org.kde.PlasmaShell.xml --- a/shell/dbus/org.kde.PlasmaShell.xml +++ b/shell/dbus/org.kde.PlasmaShell.xml @@ -1,6 +1,7 @@ + diff --git a/shell/desktopview.h b/shell/desktopview.h --- a/shell/desktopview.h +++ b/shell/desktopview.h @@ -62,7 +62,7 @@ ~DesktopView() override; /*This is different from screen() as is always there, even if the window is - temporarly outside the screen or if is hidden: only plasmashell will ever + temporarily outside the screen or if is hidden: only plasmashell will ever change this property, unlike QWindow::screen()*/ void setScreenToFollow(QScreen *screen); QScreen *screenToFollow() const; @@ -77,6 +77,8 @@ QVariantMap candidateContainmentsGraphicItems() const; + Q_INVOKABLE QString fileFromPackage(const QString &key, const QString &fileName); + protected: bool event(QEvent *e) override; void keyPressEvent(QKeyEvent *e) override; diff --git a/shell/desktopview.cpp b/shell/desktopview.cpp --- a/shell/desktopview.cpp +++ b/shell/desktopview.cpp @@ -218,6 +218,11 @@ return map; } +Q_INVOKABLE QString DesktopView::fileFromPackage(const QString &key, const QString &fileName) +{ + return corona()->kPackage().filePath(key.toUtf8(), fileName); +} + bool DesktopView::event(QEvent *e) { if (e->type() == QEvent::PlatformSurface) { diff --git a/shell/main.cpp b/shell/main.cpp --- a/shell/main.cpp +++ b/shell/main.cpp @@ -38,11 +38,11 @@ #include "shellcorona.h" #include "standaloneappcorona.h" -#include "shellmanager.h" #include "coronatesthelper.h" #include "softwarerendernotifier.h" #include +#include int main(int argc, char *argv[]) { @@ -88,6 +88,11 @@ app.setQuitOnLastWindowClosed(false); + KSharedConfig::Ptr startupConf = KSharedConfig::openConfig(QStringLiteral("plasmashellrc")); + KConfigGroup startupConfGroup(startupConf, "Shell"); + const QString defaultShell = startupConfGroup.readEntry("ShellPackage", qEnvironmentVariable("PLASMA_DEFAULT_SHELL", "org.kde.plasma.desktop")); + + bool replace = false; { QCommandLineParser cliOptions; @@ -101,7 +106,7 @@ QCommandLineOption shellPluginOption(QStringList() << QStringLiteral("p") << QStringLiteral("shell-plugin"), i18n("Force loading the given shell plugin"), - QStringLiteral("plugin")); + QStringLiteral("plugin"), defaultShell); QCommandLineOption standaloneOption(QStringList() << QStringLiteral("a") << QStringLiteral("standalone"), i18n("Load plasmashell as a standalone application, needs the shell-plugin option to be specified")); @@ -131,7 +136,9 @@ QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement); QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement); - ShellManager::s_fixedShell = cliOptions.value(shellPluginOption); + ShellCorona* corona = new ShellCorona(&app); + corona->setShell(cliOptions.value(shellPluginOption)); + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, corona, &QObject::deleteLater); if (!cliOptions.isSet(noRespawnOption) && !cliOptions.isSet(testOption)) { KCrash::setFlags(KCrash::AutoRestart); @@ -146,21 +153,15 @@ QStandardPaths::setTestModeEnabled(true); QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)).removeRecursively(); - ShellManager::s_testModeLayout = layoutUrl.toLocalFile(); + corona->setTestModeLayout(layoutUrl.toLocalFile()); qApp->setProperty("org.kde.KActivities.core.disableAutostart", true); - QObject::connect(ShellManager::instance(), &ShellManager::shellChanged, - ShellManager::instance(), - [layoutUrl]() { - new CoronaTestHelper(ShellManager::instance()->corona()); - } - ); + new CoronaTestHelper(corona); } if (cliOptions.isSet(standaloneOption)) { if (cliOptions.isSet(shellPluginOption)) { - ShellManager::s_standaloneOption = true; app.setApplicationName(QStringLiteral("plasmashell_") + cliOptions.value(shellPluginOption)); app.setQuitOnLastWindowClosed(true); @@ -176,18 +177,7 @@ qApp->setProperty("_plasma_dbus_master", true); } - if (cliOptions.isSet(replaceOption)) { - auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"), - QStringLiteral("/MainApplication"), - QStringLiteral("org.qtproject.Qt.QCoreApplication"), - QStringLiteral("quit")); - QDBusConnection::sessionBus().call(message); //deliberately block until it's done, so we register the name after the app quits - } - } - - KDBusService service(KDBusService::Unique); - - QObject::connect(ShellManager::instance(), &ShellManager::glInitializationFailed, &app, [&app]() { + QObject::connect(corona, &ShellCorona::glInitializationFailed, &app, [&app]() { //scene graphs errors come from a thread //even though we process them in the main thread, app.exit could still process these events static bool s_multipleInvokations = false; @@ -209,8 +199,12 @@ } app.exit(-1); }); + replace = cliOptions.isSet(replaceOption); + } + + KDBusService service(KDBusService::Unique | KDBusService::StartupOption(replace ? KDBusService::Replace : 0)); + SoftwareRendererNotifier::notifyIfRelevant(); - QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, ShellManager::instance(), &QObject::deleteLater); return app.exec(); } diff --git a/shell/org.kde.plasmashell.desktop b/shell/org.kde.plasmashell.desktop.cmake rename from shell/org.kde.plasmashell.desktop rename to shell/org.kde.plasmashell.desktop.cmake --- a/shell/org.kde.plasmashell.desktop +++ b/shell/org.kde.plasmashell.desktop.cmake @@ -1,5 +1,5 @@ [Desktop Entry] -Exec=plasmashell +Exec=@CMAKE_INSTALL_PREFIX@/bin/plasmashell X-DBUS-StartupType=Unique Name=Plasma Desktop Workspace Name[ar]=مساحة عمل سطح مكتب بلازما @@ -22,18 +22,18 @@ Name[hr]=Plasma radno okruženje Name[hu]=Plazma asztali munkaterület Name[ia]=Spatio de labor de scriptorio de Plasma -Name[id]=Workspace Desktop Plasma +Name[id]=Ruangkerja Desktop Plasma Name[is]=Vinnurýmd Plasma skjáborðs Name[it]=Spazio di lavoro del desktop di Plasma Name[ja]=Plasma デスクトップワークスペース Name[ko]=Plasma 데스크톱 작업 공간 -Name[lt]=Plasma darbalaukio erdvė +Name[lt]=Plasma darbalaukio darbo sritis Name[nb]=Arbeidsrom for Plasma skrivebord Name[nds]=Plasma-Schriefdischarbeitrebeet Name[nl]=Plasma Bureaublad Werkplek Name[nn]=Arbeidsområde for Plasma-skrivebord Name[pa]=ਪਲਾਜ਼ਮਾ ਡੈਸਕਟਾਪ ਵਰਕਸਪੇਸ -Name[pl]=Przestrzeń Robocza Pulpitu Plazmy +Name[pl]=Przestrzeń Pracy Pulpitu Plazmy Name[pt]=Área de Trabalho do Plasma Name[pt_BR]=Espaço de Trabalho Plasma Name[ru]=Рабочая среда Plasma @@ -44,6 +44,7 @@ Name[sr@ijekavianlatin]=Plasma, radni prostor površi Name[sr@latin]=Plasma, radni prostor površi Name[sv]=Plasma arbetsområde för skrivbordet +Name[tg]=Фазои мизи кории Плазма Name[tr]=Plasma Masaüstü Çalışma Alanı Name[uk]=Робочий простір Плазми для комп’ютерів Name[x-test]=xxPlasma Desktop Workspacexx @@ -56,3 +57,5 @@ X-KDE-autostart-phase=0 Icon=plasma NoDisplay=true + +X-KDE-Wayland-Interfaces=org_kde_plasma_window_management,org_kde_kwin_keystate diff --git a/shell/packageplugins/lookandfeel/lookandfeel.cpp b/shell/packageplugins/lookandfeel/lookandfeel.cpp --- a/shell/packageplugins/lookandfeel/lookandfeel.cpp +++ b/shell/packageplugins/lookandfeel/lookandfeel.cpp @@ -27,7 +27,7 @@ void LookAndFeelPackage::initPackage(KPackage::Package *package) { - // http://community.kde.org/Plasma/lookAndFeelPackage# + // https://community.kde.org/Plasma/lookAndFeelPackage# package->setDefaultPackageRoot(QStringLiteral("plasma/look-and-feel/")); //Defaults diff --git a/shell/packageplugins/lookandfeel/plasma-packagestructure-lookandfeel.desktop b/shell/packageplugins/lookandfeel/plasma-packagestructure-lookandfeel.desktop --- a/shell/packageplugins/lookandfeel/plasma-packagestructure-lookandfeel.desktop +++ b/shell/packageplugins/lookandfeel/plasma-packagestructure-lookandfeel.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Look and Feel Name[ar]=المظهر و الأحساس +Name[ast]=Aspeutu y estilu Name[bs]=Izgled i osjećaj Name[ca]=Aspecte i comportament Name[ca@valencia]=Aspecte i comportament @@ -19,19 +20,20 @@ Name[he]=מראה ותחושה Name[hu]=Megjelenés Name[ia]=Semblantia -Name[id]=Nuansa dan Suasana +Name[id]=Look and Feel Name[is]=Útlit og viðmót Name[it]=Aspetto Name[ja]=外観 Name[kk]=Сыртқы көрнісі Name[ko]=모습과 느낌 -Name[lt]=Išvaizda ir pojūtis +Name[lt]=Išvaizda ir turinys +Name[lv]=Izskats un izjūtas Name[nb]=Utseende og oppførsel Name[nds]=Utsehn un Bedenen Name[nl]=Uiterlijk en gedrag Name[nn]=Utsjånad og åtferd Name[pa]=ਦਿੱਖ ਅਤੇ ਮਹਿਸੂਸ -Name[pl]=Wrażenia wzrokowe i dotykowe +Name[pl]=Zestaw wyglądu Name[pt]=Aparência e Comportamento Name[pt_BR]=Aparência Name[ro]=Aspect și comportament @@ -43,6 +45,7 @@ Name[sr@ijekavianlatin]=Izgled i osećaj Name[sr@latin]=Izgled i osećaj Name[sv]=Utseende och känsla +Name[tg]=Намуди зоҳирӣ Name[tr]=Bakın ve Hissedin Name[uk]=Вигляд і поведінка Name[x-test]=xxLook and Feelxx diff --git a/shell/packageplugins/qmlWallpaper/plasma-packagestructure-wallpaper.desktop b/shell/packageplugins/qmlWallpaper/plasma-packagestructure-wallpaper.desktop --- a/shell/packageplugins/qmlWallpaper/plasma-packagestructure-wallpaper.desktop +++ b/shell/packageplugins/qmlWallpaper/plasma-packagestructure-wallpaper.desktop @@ -1,5 +1,6 @@ [Desktop Entry] Name=Wallpaper +Name[ast]=Fondu de pantalla Name[bs]=Pozadina Name[ca]=Fons de pantalla Name[ca@valencia]=Fons de pantalla @@ -21,7 +22,8 @@ Name[it]=Sfondo Name[ja]=壁紙 Name[ko]=배경 그림 -Name[lt]=Apmušalas +Name[lt]=Darbalaukio fonas +Name[lv]=Tapete Name[nb]=Tapet Name[nds]=Achtergrundbild Name[nl]=Achtergrondafbeelding diff --git a/shell/packageplugins/shell/plasma-packagestructure-plasma-shell.desktop b/shell/packageplugins/shell/plasma-packagestructure-plasma-shell.desktop --- a/shell/packageplugins/shell/plasma-packagestructure-plasma-shell.desktop +++ b/shell/packageplugins/shell/plasma-packagestructure-plasma-shell.desktop @@ -1,8 +1,9 @@ [Desktop Entry] Name=Plasma Shell +Name[ast]=Shell de Plasma Name[bs]=Plazma školjka Name[ca]=Intèrpret d'ordres del Plasma -Name[ca@valencia]=Intèrpret d'ordres del Plasma +Name[ca@valencia]=Intèrpret d'ordres de Plasma Name[cs]=Shell Plasmy Name[da]=Plasma-skal Name[de]=Plasma-Umgebung @@ -13,14 +14,15 @@ Name[eu]=Plasma-ingurunea Name[fi]=Plasma-kuori Name[fr]=Environnement Plasma -Name[gl]=Intérprete de ordes de Plasma +Name[gl]=Shell de Plasma Name[hu]=Plasma felület Name[id]=Shell Plasma Name[is]=Plasma skel Name[it]=Shell di Plasma Name[ja]=Plasma シェル Name[ko]=Plasma 셸 Name[lt]=Plasma apvalkalas +Name[lv]=Plasma čaula Name[nb]=Plasma-skall Name[nds]=Plasma-Konsool Name[nl]=Plasma Shell diff --git a/shell/packageplugins/shell/shellpackage.cpp b/shell/packageplugins/shell/shellpackage.cpp --- a/shell/packageplugins/shell/shellpackage.cpp +++ b/shell/packageplugins/shell/shellpackage.cpp @@ -77,9 +77,10 @@ } const QString pluginName = package->metadata().pluginId(); - if (!pluginName.isEmpty() && pluginName != DEFAULT_SHELL) { - KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Shell"), DEFAULT_SHELL); + const QString fallback = package->metadata().value("X-Plasma-FallbackPackage", DEFAULT_SHELL); + + KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Shell"), fallback); package->setFallbackPackage(pkg); } else if (package->fallbackPackage().isValid() && pluginName == DEFAULT_SHELL) { package->setFallbackPackage(KPackage::Package()); diff --git a/shell/packageplugins/wallpaperimages/plasma-packagestructure-wallpaperimages.desktop b/shell/packageplugins/wallpaperimages/plasma-packagestructure-wallpaperimages.desktop --- a/shell/packageplugins/wallpaperimages/plasma-packagestructure-wallpaperimages.desktop +++ b/shell/packageplugins/wallpaperimages/plasma-packagestructure-wallpaperimages.desktop @@ -16,12 +16,13 @@ Name[gl]=Fondos de escritorio Name[he]=תמונת רקע Name[hu]=Háttérképek -Name[id]=Image Wallpaper +Name[id]=Citra Wallpaper Name[is]=Bakgrunnsmyndir Name[it]=Immagini di sfondo Name[ja]=壁紙画像 Name[ko]=배경 그림 -Name[lt]=Darbalaukio paveiksliukai +Name[lt]=Darbalaukio fono paveikslai +Name[lv]=Tapešu attēli Name[nb]=Tapetbilder Name[nl]=Achtergrondafbeeldingen Name[nn]=Bakgrunnsbilete diff --git a/shell/panelconfigview.cpp b/shell/panelconfigview.cpp --- a/shell/panelconfigview.cpp +++ b/shell/panelconfigview.cpp @@ -51,7 +51,7 @@ setScreen(panelView->screen()); - connect(panelView, SIGNAL(screenChanged(QScreen *)), &m_screenSyncTimer, SLOT(start())); + connect(panelView, &QWindow::screenChanged, &m_screenSyncTimer, QOverload<>::of(&QTimer::start)); m_screenSyncTimer.setSingleShot(true); m_screenSyncTimer.setInterval(150); connect(&m_screenSyncTimer, &QTimer::timeout, diff --git a/shell/panelcountsource.h b/shell/panelcountsource.h new file mode 100644 --- /dev/null +++ b/shell/panelcountsource.h @@ -0,0 +1,45 @@ +/* + * Copyright 2019 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PANELCOUNTSOURCE_H +#define PANELCOUNTSOURCE_H + +#include "shellcorona.h" +#include +#include + +class PanelCountSource : public KUserFeedback::AbstractDataSource +{ +public: + /*! Create a new start count data source. */ + PanelCountSource(ShellCorona* corona) + : AbstractDataSource(QStringLiteral("panelCount"), KUserFeedback::Provider::DetailedSystemInformation) + , corona(corona) + {} + + QString name() const override { return i18n("Panel Count"); } + QString description() const override { return i18n("Counts the panels"); } + + QVariant data() override { return QVariantMap{ { QStringLiteral("panelCount"), corona->panelCount() } } ; } + +private: + ShellCorona* const corona; +}; + +#endif diff --git a/shell/panelshadows.cpp b/shell/panelshadows.cpp --- a/shell/panelshadows.cpp +++ b/shell/panelshadows.cpp @@ -75,7 +75,6 @@ void clearShadowX11(const QWindow *window); void clearShadowWayland(const QWindow *window); void updateShadows(); - void windowDestroyed(QObject *deletedObject); void setupData(Plasma::FrameSvg::EnabledBorders enabledBorders); bool hasShadows() const; @@ -130,7 +129,9 @@ d(new Private(this)) { setImagePath(prefix); - connect(this, SIGNAL(repaintNeeded()), this, SLOT(updateShadows())); + connect(this, &Plasma::Svg::repaintNeeded, this, [this]() { + d->updateShadows(); + }); } PanelShadows::~PanelShadows() @@ -151,8 +152,12 @@ d->m_windows[window] = enabledBorders; d->updateShadow(window, enabledBorders); - connect(window, SIGNAL(destroyed(QObject*)), - this, SLOT(windowDestroyed(QObject*)), Qt::UniqueConnection); + connect(window, &QObject::destroyed, this, [this, window]() { + d->m_windows.remove(window); + if (d->m_windows.isEmpty()) { + d->clearPixmaps(); + } + }); } void PanelShadows::removeWindow(const QWindow *window) @@ -180,16 +185,6 @@ d->updateShadow(window, enabledBorders); } - -void PanelShadows::Private::windowDestroyed(QObject *deletedObject) -{ - m_windows.remove(static_cast(deletedObject)); - - if (m_windows.isEmpty()) { - clearPixmaps(); - } -} - void PanelShadows::Private::updateShadows() { const bool hadShadowsBefore = !m_shadowPixmaps.isEmpty(); diff --git a/shell/panelshadows_p.h b/shell/panelshadows_p.h --- a/shell/panelshadows_p.h +++ b/shell/panelshadows_p.h @@ -43,9 +43,6 @@ private: class Private; Private * const d; - - Q_PRIVATE_SLOT(d, void updateShadows()) - Q_PRIVATE_SLOT(d, void windowDestroyed(QObject *deletedObject)) }; #endif diff --git a/shell/panelview.h b/shell/panelview.h --- a/shell/panelview.h +++ b/shell/panelview.h @@ -114,7 +114,9 @@ ~PanelView() override; KConfigGroup config() const override; + KConfigGroup configDefaults() const; + Q_INVOKABLE QString fileFromPackage(const QString &key, const QString &fileName); Q_INVOKABLE void maximize(); Qt::Alignment alignment() const; @@ -151,13 +153,14 @@ */ QRect geometryByDistance(int distance) const; - /* Shared with script/panel.cpp */ + /* Both Shared with script/panel.cpp */ static KConfigGroup panelConfig(ShellCorona *corona, Plasma::Containment *containment, QScreen *screen); + static KConfigGroup panelConfigDefaults(ShellCorona *corona, Plasma::Containment *containment, QScreen *screen); void updateStruts(); /*This is different from screen() as is always there, even if the window is - temporarly outside the screen or if is hidden: only plasmashell will ever + temporarily outside the screen or if is hidden: only plasmashell will ever change this property, unlike QWindow::screen()*/ void setScreenToFollow(QScreen* screen); QScreen* screenToFollow() const; @@ -202,16 +205,17 @@ void adaptToScreen(); void handleQmlStatusChange(QQmlComponent::Status status); void updateMask(); + void updateEnabledBorders(); private: + int readConfigValueWithFallBack(const QString &key, int defaultValue); void resizePanel(); void integrateScreen(); bool containmentContainsPosition(const QPointF &point) const; QPointF positionAdjustedForContainment(const QPointF &point) const; void setupWaylandIntegration(); void visibilityModeToWayland(); bool edgeActivated() const; - void updateEnabledBorders(); bool canSetStrut() const; int m_offset; diff --git a/shell/panelview.cpp b/shell/panelview.cpp --- a/shell/panelview.cpp +++ b/shell/panelview.cpp @@ -80,6 +80,7 @@ connect(&m_theme, &Plasma::Theme::themeChanged, this, &PanelView::updateMask); connect(this, &PanelView::backgroundHintsChanged, this, &PanelView::updateMask); + connect(this, &PanelView::backgroundHintsChanged, this, &PanelView::updateEnabledBorders); // TODO: add finished/componentComplete signal to QuickViewSharedEngine, // so we exactly know when rootobject is available connect(this, &QuickViewSharedEngine::statusChanged, @@ -136,11 +137,39 @@ } } +KConfigGroup PanelView::panelConfigDefaults(ShellCorona *corona, Plasma::Containment *containment, QScreen *screen) +{ + if (!containment || !screen) { + return KConfigGroup(); + } + + KConfigGroup views(corona->applicationConfig(), "PlasmaViews"); + views = KConfigGroup(&views, QStringLiteral("Panel %1").arg(containment->id())); + + return KConfigGroup(&views, QStringLiteral("Defaults")); +} + +int PanelView::readConfigValueWithFallBack(const QString &key, int defaultValue) +{ + int value = config().readEntry(key, configDefaults().readEntry(key, defaultValue)); + return value; +} + KConfigGroup PanelView::config() const { return panelConfig(m_corona, containment(), m_screenToFollow); } +KConfigGroup PanelView::configDefaults() const +{ + return panelConfigDefaults(m_corona, containment(), m_screenToFollow); +} + +Q_INVOKABLE QString PanelView::fileFromPackage(const QString &key, const QString &fileName) +{ + return corona()->kPackage().filePath(key.toUtf8(), fileName); +} + void PanelView::maximize() { int length; @@ -166,7 +195,7 @@ } m_alignment = alignment; - //alignment is not resolution dependent + //alignment is not resolution dependent, doesn't save to Defaults config().parent().writeEntry("alignment", (int)m_alignment); emit alignmentChanged(); positionPanel(); @@ -195,6 +224,7 @@ m_offset = offset; config().writeEntry("offset", m_offset); + configDefaults().writeEntry("offset", m_offset); positionPanel(); emit offsetChanged(); m_corona->requestApplicationConfigSync(); @@ -216,6 +246,7 @@ emit thicknessChanged(); config().writeEntry("thickness", value); + configDefaults().writeEntry("thickness", value); m_corona->requestApplicationConfigSync(); resizePanel(); } @@ -252,6 +283,7 @@ } config().writeEntry("maxLength", length); + configDefaults().writeEntry("maxLength", length); m_maxLength = length; emit maximumLengthChanged(); m_corona->requestApplicationConfigSync(); @@ -275,6 +307,7 @@ } config().writeEntry("minLength", length); + configDefaults().writeEntry("minLength", length); m_minLength = length; emit minimumLengthChanged(); m_corona->requestApplicationConfigSync(); @@ -333,7 +366,7 @@ } if (config().isValid() && config().parent().isValid()) { - //panelVisibility is not resolution dependent + //panelVisibility is not resolution dependent, don't write to Defaults config().parent().writeEntry("panelVisibility", (int)mode); m_corona->requestApplicationConfigSync(); } @@ -459,7 +492,7 @@ case Plasma::Types::RightEdge: switch (m_alignment) { case Qt::AlignCenter: - // Never use rect.right(); for historical reasons it returns left() + width() - 1; see http://doc.qt.io/qt-5/qrect.html#right + // Never use rect.right(); for historical reasons it returns left() + width() - 1; see https://doc.qt.io/qt-5/qrect.html#right position = QPoint(QPoint(screenGeometry.x() + screenGeometry.width(), screenGeometry.center().y()) - QPoint(thickness() + distance, 0) + QPoint(0, m_offset - height()/2)); break; case Qt::AlignRight: @@ -532,34 +565,40 @@ return; } - //defaults, may be altered by values written by the scripting in startup phase - const int defaultOffset = 0; - const int defaultAlignment = Qt::AlignLeft; + // All the defaults are based on whatever are the current values + // so won't be weirdly reset after screen resolution change + //alignment is not resolution dependent //but if fails read it from the resolution dependent one as //the place for this config key is changed in Plasma 5.9 - setAlignment((Qt::Alignment)config().parent().readEntry("alignment", config().readEntry("alignment", defaultAlignment))); - m_offset = config().readEntry("offset", defaultOffset); + //Do NOT use readConfigValueWithFallBack + setAlignment((Qt::Alignment)config().parent().readEntry("alignment", config().readEntry("alignment", m_alignment))); + + // All the other values are read from screen independent values, + // but fallback on the screen independent section, as is the only place + // is safe to directly write during plasma startup, as there can be + // resolution changes + m_offset = readConfigValueWithFallBack("offset", m_offset); if (m_alignment != Qt::AlignCenter) { m_offset = qMax(0, m_offset); } - const int defaultThickness = 30; - setThickness(config().readEntry("thickness", defaultThickness)); + setThickness(readConfigValueWithFallBack("thickness", m_thickness)); const QSize screenSize = m_screenToFollow->size(); setMinimumSize(QSize(-1, -1)); //FIXME: an invalid size doesn't work with QWindows setMaximumSize(screenSize); const int side = containment()->formFactor() == Plasma::Types::Vertical ? screenSize.height() : screenSize.width(); const int maxSize = side - m_offset; - m_maxLength = qBound(MINSIZE, config().readEntry("maxLength", side), maxSize); - m_minLength = qBound(MINSIZE, config().readEntry("minLength", side), maxSize); + m_maxLength = qBound(MINSIZE, readConfigValueWithFallBack("maxLength", side), maxSize); + m_minLength = qBound(MINSIZE, readConfigValueWithFallBack("minLength", side), maxSize); //panelVisibility is not resolution dependent //but if fails read it from the resolution dependent one as //the place for this config key is changed in Plasma 5.9 + //Do NOT use readConfigValueWithFallBack setVisibilityMode((VisibilityMode)config().parent().readEntry("panelVisibility", config().readEntry("panelVisibility", (int)NormalPanel))); m_initCompleted = true; resizePanel(); @@ -864,7 +903,7 @@ if (!containmentContainsPosition(we->pos())) { auto we2 = new QWheelEvent(positionAdjustedForContainment(we->pos()), positionAdjustedForContainment(we->pos()) + position(), - we->pixelDelta(), we->angleDelta(), we->delta(), + we->pixelDelta(), we->angleDelta(), we->angleDelta().y(), we->orientation(), we->buttons(), we->modifiers(), we->phase()); QCoreApplication::postEvent(this, we2); @@ -1283,12 +1322,7 @@ } if (m_enabledBorders != borders) { - if (m_backgroundHints == Plasma::Types::NoBackground) { - PanelShadows::self()->removeWindow(this); - } else { - PanelShadows::self()->setEnabledBorders(this, borders); - } - + PanelShadows::self()->setEnabledBorders(this, borders); m_enabledBorders = borders; emit enabledBordersChanged(); } diff --git a/shell/scripting/containment.h b/shell/scripting/containment.h --- a/shell/scripting/containment.h +++ b/shell/scripting/containment.h @@ -76,7 +76,7 @@ void setWallpaperMode(const QString &wallpaperMode); Q_INVOKABLE QJSValue widgetById(const QJSValue ¶mId = QJSValue()) const; - Q_INVOKABLE QJSValue addWidget(const QJSValue &v = QJSValue(), qreal x = -1, qreal y = -1, qreal w = -1, qreal h = -1); + Q_INVOKABLE QJSValue addWidget(const QJSValue &v = QJSValue(), qreal x = -1, qreal y = -1, qreal w = -1, qreal h = -1, const QVariantList &args = QVariantList()); Q_INVOKABLE QJSValue widgets(const QString &widgetType = QString()) const; public Q_SLOTS: diff --git a/shell/scripting/containment.cpp b/shell/scripting/containment.cpp --- a/shell/scripting/containment.cpp +++ b/shell/scripting/containment.cpp @@ -173,7 +173,7 @@ return QJSValue(); } -QJSValue Containment::addWidget(const QJSValue &v, qreal x, qreal y, qreal w, qreal h) +QJSValue Containment::addWidget(const QJSValue &v, qreal x, qreal y, qreal w, qreal h, const QVariantList &args) { if (!v.isString() && !v.isQObject()) { return d->engine->newError(i18n("addWidget requires a name of a widget or a widget object")); @@ -194,7 +194,7 @@ containmentItem = d->containment.data()->property("_plasma_graphicObject").value(); if (containmentItem) { - QMetaObject::invokeMethod(containmentItem , "createApplet", Qt::DirectConnection, Q_RETURN_ARG(Plasma::Applet *, applet), Q_ARG(QString, v.toString()), Q_ARG(QVariantList, QVariantList()), Q_ARG(QRectF, geometry)); + QMetaObject::invokeMethod(containmentItem , "createApplet", Qt::DirectConnection, Q_RETURN_ARG(Plasma::Applet *, applet), Q_ARG(QString, v.toString()), Q_ARG(QVariantList, args), Q_ARG(QRectF, geometry)); } if (applet) { return d->engine->wrap(applet); @@ -205,7 +205,7 @@ //Case in which either: // * a geometry wasn't provided // * containmentItem wasn't found - applet = d->containment.data()->createApplet(v.toString()); + applet = d->containment.data()->createApplet(v.toString(), args); if (applet) { return d->engine->wrap(applet); diff --git a/shell/scripting/panel.h b/shell/scripting/panel.h --- a/shell/scripting/panel.h +++ b/shell/scripting/panel.h @@ -94,6 +94,8 @@ private: PanelView *panel() const; KConfigGroup panelConfig() const; + KConfigGroup panelConfigDefaults() const; + ShellCorona *m_corona; }; diff --git a/shell/scripting/panel.cpp b/shell/scripting/panel.cpp --- a/shell/scripting/panel.cpp +++ b/shell/scripting/panel.cpp @@ -112,7 +112,7 @@ return corona()->panelView(c); } - +// NOTE: this is used *only* for alignment and visibility KConfigGroup Panel::panelConfig() const { int screenNum = qMax(screen(), 0); //if we don't have a screen (-1) we'll be put on screen 0 @@ -124,9 +124,29 @@ return PanelView::panelConfig(corona(), containment(), s); } +//NOTE: when we don't have a view we should write only to the defaults group as we don't know yet during startup if we are on the "final" screen resolution yet +KConfigGroup Panel::panelConfigDefaults() const +{ + int screenNum = qMax(screen(), 0); //if we don't have a screen (-1) we'll be put on screen 0 + + if (QGuiApplication::screens().size() < screenNum) { + return KConfigGroup(); + } + QScreen *s = QGuiApplication::screens().at(screenNum); + return PanelView::panelConfigDefaults(corona(), containment(), s); +} + +// NOTE: Alignment is the only one that reads and writes directly from panelconfig() QString Panel::alignment() const { - switch (panelConfig().readEntry("alignment", 0)) { + int alignment; + if (panel()) { + alignment = panel()->alignment(); + } else { + alignment = panelConfig().readEntry("alignment", 0); + } + + switch (alignment) { case Qt::AlignRight: return "right"; case Qt::AlignCenter: @@ -138,6 +158,7 @@ return "left"; } +// NOTE: Alignment is the only one that reads and writes directly from panelconfig() void Panel::setAlignment(const QString &alignment) { int a = Qt::AlignLeft; @@ -147,80 +168,117 @@ a = Qt::AlignCenter; } - panelConfig().writeEntry("alignment", a); + // Always prefer the view, if available if (panel()) { - QMetaObject::invokeMethod(panel(), "restore"); + panel()->setAlignment(Qt::Alignment(a)); + } else { + panelConfig().writeEntry("alignment", a); } } + +// From now on only panelConfigDefaults should be used int Panel::offset() const { - return panelConfig().readEntry("offset", 0); + if (panel()) { + return panel()->offset(); + } else { + return panelConfigDefaults().readEntry("offset", 0); + } } void Panel::setOffset(int pixels) { - panelConfig().writeEntry("offset", pixels); - if (panel()) { - QMetaObject::invokeMethod(panel(), "restore"); - } + panelConfigDefaults().writeEntry("offset", pixels); + if (panel()) { + panel()->setOffset(pixels); + } else { + panelConfigDefaults().readEntry("offset", pixels); + } } int Panel::length() const { - return panelConfig().readEntry("length", 0); + if (panel()) { + return panel()->length(); + } else { + return panelConfigDefaults().readEntry("length", 0); + } } void Panel::setLength(int pixels) { - panelConfig().writeEntry("length", pixels); - if (panel()) { - QMetaObject::invokeMethod(panel(), "restore"); - } + if (panel()) { + panel()->setLength(pixels); + } else { + panelConfigDefaults().writeEntry("length", pixels); + } } int Panel::minimumLength() const { - return panelConfig().readEntry("minLength", 0); + if (panel()) { + return panel()->minimumLength(); + } else { + return panelConfigDefaults().readEntry("minLength", 0); + } } void Panel::setMinimumLength(int pixels) { - panelConfig().writeEntry("minLength", pixels); - if (panel()) { - QMetaObject::invokeMethod(panel(), "restore"); - } + if (panel()) { + panel()->setMinimumLength(pixels); + } else { + panelConfigDefaults().writeEntry("minLength", pixels); + } } int Panel::maximumLength() const { - return panelConfig().readEntry("maxLength", 0); + if (panel()) { + return panel()->maximumLength(); + } else { + return panelConfigDefaults().readEntry("maxLength", 0); + } } void Panel::setMaximumLength(int pixels) { - panelConfig().writeEntry("maxLength", pixels); - if (panel()) { - QMetaObject::invokeMethod(panel(), "restore"); - } + if (panel()) { + panel()->setMaximumLength(pixels); + } else { + panelConfigDefaults().writeEntry("maxLength", pixels); + } } int Panel::height() const { - return panelConfig().readEntry("thickness", 0); + if (panel()) { + return panel()->thickness(); + } else { + return panelConfigDefaults().readEntry("thickness", 0); + } } void Panel::setHeight(int height) { - panelConfig().writeEntry("thickness", height); if (panel()) { - QMetaObject::invokeMethod(panel(), "restore"); + panel()->setThickness(height); + } else { + panelConfigDefaults().writeEntry("thickness", height); } } QString Panel::hiding() const { - switch (panelConfig().readEntry("panelVisibility", 0)) { + int visibility; + if (panel()) { + visibility = panel()->visibilityMode(); + } else { + visibility = panelConfig().readEntry("panelVisibility", 0); + } + + switch (visibility) { case PanelView::NormalPanel: return "none"; case PanelView::AutoHide: @@ -244,9 +302,10 @@ visibilityMode = PanelView::WindowsGoBelow; } - panelConfig().writeEntry("panelVisibility", (int)visibilityMode); if (panel()) { - QMetaObject::invokeMethod(panel(), "restore"); + panel()->setVisibilityMode(visibilityMode); + } else { + panelConfig().writeEntry("panelVisibility", (int)visibilityMode); } } diff --git a/shell/scripting/scriptengine.cpp b/shell/scripting/scriptengine.cpp --- a/shell/scripting/scriptengine.cpp +++ b/shell/scripting/scriptengine.cpp @@ -267,7 +267,7 @@ QString error = i18n("Error: %1 at line %2\n\nBacktrace:\n%3", result.toString(), result.property("lineNumber").toInt(), - result.property("stack").toVariant().value().join(QStringLiteral("\n "))); + result.property("stack").toVariant().value().join(QLatin1String("\n "))); emit printError(error); emit exception(result); m_errorString = error; diff --git a/shell/scripting/scriptengine_v1.cpp b/shell/scripting/scriptengine_v1.cpp --- a/shell/scripting/scriptengine_v1.cpp +++ b/shell/scripting/scriptengine_v1.cpp @@ -448,7 +448,7 @@ for (const auto c : result) { // make really sure we get actual desktops, so check for a non empty - // activty id + // activity id if (!isPanel(c) && !c->activity().isEmpty()) { containments.setProperty(count, m_engine->wrap(c)); ++count; @@ -487,14 +487,14 @@ bool ScriptEngine::V1::loadTemplate(const QString &layout) { - if (layout.isEmpty() || layout.contains(QStringLiteral("'"))) { + if (layout.isEmpty() || layout.contains(QLatin1Char('\''))) { // qDebug() << "layout is empty"; return false; } auto filter = [&layout](const KPluginMetaData &md) -> bool { - return md.pluginId() == layout && KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-ContainmentCategories")).contains(QStringLiteral("panel")); + return md.pluginId() == layout && KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-ContainmentCategories")).contains(QLatin1String("panel")); }; QList offers = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); @@ -510,7 +510,7 @@ { ShellCorona *sc = qobject_cast(m_engine->m_corona); if (sc) { - const QString overridePackagePath = sc->lookAndFeelPackage().path() + QStringLiteral("contents/layouts/") + pluginData.pluginId(); + const QString overridePackagePath = sc->lookAndFeelPackage().path() + QLatin1String("contents/layouts/") + pluginData.pluginId(); path = overridePackagePath + QStringLiteral("/metadata.json"); if (QFile::exists(path)) { @@ -580,7 +580,7 @@ return true; } - if (application.contains(QStringLiteral("'"))) { + if (application.contains(QLatin1Char('\''))) { // apostrophes just screw up the trader lookups below, so check for it return false; } @@ -648,7 +648,7 @@ browserApp = storageId ? htmlApp->storageId() : htmlApp->exec(); } } else if (browserApp.startsWith('!')) { - browserApp = browserApp.mid(1); + browserApp.remove(0, 1); } return onlyExec(browserApp); @@ -725,7 +725,7 @@ service->entryPath()); } - if (application.contains(QStringLiteral("'"))) { + if (application.contains(QLatin1Char('\''))) { // apostrophes just screw up the trader lookups below, so check for it return QString(); } diff --git a/shell/scripting/widget.h b/shell/scripting/widget.h --- a/shell/scripting/widget.h +++ b/shell/scripting/widget.h @@ -49,6 +49,7 @@ Q_PROPERTY(QStringList currentConfigGroup WRITE setCurrentConfigGroup READ currentConfigGroup) Q_PROPERTY(QString globalShortcut WRITE setGlobalShortcut READ globalShorcut) Q_PROPERTY(bool locked READ locked WRITE setLocked) + Q_PROPERTY(QString userBackgroundHints WRITE setUserBackgroundHints READ userBackgroundHints) public: explicit Widget(Plasma::Applet *applet, QObject *parent = nullptr); @@ -66,6 +67,9 @@ void setGlobalShortcut(const QString &shortcut); QString globalShorcut() const; + QString userBackgroundHints() const; + void setUserBackgroundHints(QString hint); + Plasma::Applet *applet() const override; public Q_SLOTS: diff --git a/shell/scripting/widget.cpp b/shell/scripting/widget.cpp --- a/shell/scripting/widget.cpp +++ b/shell/scripting/widget.cpp @@ -20,6 +20,7 @@ #include "widget.h" #include +#include #include #include @@ -176,6 +177,22 @@ }*/ } +QString Widget::userBackgroundHints() const +{ + QMetaEnum hintEnum = QMetaEnum::fromType(); + return hintEnum.valueToKey(applet()->userBackgroundHints()); +} + +void Widget::setUserBackgroundHints(QString hint) +{ + QMetaEnum hintEnum = QMetaEnum::fromType(); + bool ok; + int value = hintEnum.keyToValue(hint.toUtf8().constData(), &ok); + if (ok) { + applet()->setUserBackgroundHints(Plasma::Types::BackgroundHints(value)); + } +} + } diff --git a/shell/shellcorona.h b/shell/shellcorona.h --- a/shell/shellcorona.h +++ b/shell/shellcorona.h @@ -38,6 +38,7 @@ class QMenu; class QScreen; class ScreenPool; +class StrutManager; namespace KActivities { @@ -89,6 +90,10 @@ Q_INVOKABLE QRegion availableScreenRegion(int id) const override; Q_INVOKABLE QRect availableScreenRect(int id) const override; + // plasmashellCorona's value + QRegion _availableScreenRegion(int id) const; + QRect _availableScreenRect(int id) const; + Q_INVOKABLE QStringList availableActivities() const; PanelView *panelView(Plasma::Containment *containment) const; @@ -162,6 +167,10 @@ void previousActivity(); void stopCurrentActivity(); + void setTestModeLayout(const QString &layout) { m_testModeLayout = layout; } + + int panelCount() const { return m_panelViews.count(); } + protected Q_SLOTS: /** * Loads the layout and performs the needed checks @@ -251,6 +260,9 @@ KWayland::Client::PlasmaShell *m_waylandPlasmaShell; bool m_closingDown : 1; + QString m_testModeLayout; + + StrutManager *m_strutManager; }; #endif // SHELLCORONA_H diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp --- a/shell/shellcorona.cpp +++ b/shell/shellcorona.cpp @@ -21,6 +21,7 @@ */ #include "shellcorona.h" +#include "strutmanager.h" #include @@ -30,6 +31,7 @@ #include #include #include +#include #include #include @@ -51,6 +53,19 @@ #include #include +#ifdef WITH_KUSERFEEDBACKCORE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "panelcountsource.h" +#endif + #include #include @@ -63,7 +78,6 @@ #include "desktopview.h" #include "panelview.h" #include "scripting/scriptengine.h" -#include "shellmanager.h" #include "osd.h" #include "screenpool.h" @@ -83,7 +97,6 @@ #include #endif - static const int s_configSyncDelay = 10000; // 10 seconds ShellCorona::ShellCorona(QObject *parent) @@ -95,7 +108,8 @@ m_addPanelsMenu(nullptr), m_interactiveConsole(nullptr), m_waylandPlasmaShell(nullptr), - m_closingDown(false) + m_closingDown(false), + m_strutManager(new StrutManager(this)) { setupWaylandIntegration(); qmlRegisterUncreatableType("org.kde.plasma.shell", 2, 0, "Desktop", QStringLiteral("It is not possible to create objects of type Desktop")); @@ -174,7 +188,7 @@ connect(activityAction, &QAction::triggered, this, &ShellCorona::toggleActivityManager); activityAction->setText(i18n("Activities...")); - activityAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-activities"))); + activityAction->setIcon(QIcon::fromTheme(QStringLiteral("activities"))); activityAction->setData(Plasma::Types::ConfigureAction); activityAction->setShortcut(QKeySequence(QStringLiteral("alt+d, alt+a"))); activityAction->setShortcutContext(Qt::ApplicationShortcut); @@ -189,9 +203,26 @@ stopActivityAction->setData(Plasma::Types::ControlAction); stopActivityAction->setVisible(false); - KGlobalAccel::self()->setGlobalShortcut(stopActivityAction, Qt::META + Qt::Key_S); + QAction *previousActivityAction = actions()->addAction(QStringLiteral("switch to previous activity")); + connect(previousActivityAction, &QAction::triggered, + this, &ShellCorona::previousActivity); + previousActivityAction->setText(i18n("Switch to Previous Activity")); + previousActivityAction->setData(Plasma::Types::ConfigureAction); + previousActivityAction->setShortcutContext(Qt::ApplicationShortcut); + + KGlobalAccel::self()->setGlobalShortcut(previousActivityAction, QKeySequence()); + + QAction *nextActivityAction = actions()->addAction(QStringLiteral("switch to next activity")); + connect(nextActivityAction, &QAction::triggered, + this, &ShellCorona::nextActivity); + nextActivityAction->setText(i18n("Switch to Next Activity")); + nextActivityAction->setData(Plasma::Types::ConfigureAction); + nextActivityAction->setShortcutContext(Qt::ApplicationShortcut); + + KGlobalAccel::self()->setGlobalShortcut(nextActivityAction, QKeySequence()); + connect(m_activityController, &KActivities::Controller::currentActivityChanged, this, &ShellCorona::currentActivityChanged); connect(m_activityController, &KActivities::Controller::activityAdded, this, &ShellCorona::activityAdded); connect(m_activityController, &KActivities::Controller::activityRemoved, this, &ShellCorona::activityRemoved); @@ -216,6 +247,19 @@ KDirWatch::self()->addFile(m_configPath); connect(KDirWatch::self(), &KDirWatch::dirty, this, &ShellCorona::configurationChanged); connect(KDirWatch::self(), &KDirWatch::created, this, &ShellCorona::configurationChanged); + + connect(qApp, &QGuiApplication::focusWindowChanged, + this, [this] (QWindow *focusWindow) { + if (!focusWindow) { + setEditMode(false); + } + } + ); + connect(this, &ShellCorona::editModeChanged, + this, [this](bool edit) { + setDashboardShown(edit); + } + ); } ShellCorona::~ShellCorona() @@ -273,6 +317,28 @@ t->setThemeName(themeName); } +#ifdef WITH_KUSERFEEDBACKCORE + auto feedbackProvider = new KUserFeedback::Provider(this); + feedbackProvider->setProductIdentifier(QStringLiteral("org.kde.plasmashell")); + feedbackProvider->setFeedbackServer(QUrl(QStringLiteral("https://telemetry.kde.org/"))); + feedbackProvider->setSubmissionInterval(7); + feedbackProvider->setApplicationStartsUntilEncouragement(5); + feedbackProvider->setEncouragementDelay(30); + feedbackProvider->addDataSource(new KUserFeedback::ApplicationVersionSource); + feedbackProvider->addDataSource(new KUserFeedback::CompilerInfoSource); + feedbackProvider->addDataSource(new KUserFeedback::PlatformInfoSource); + feedbackProvider->addDataSource(new KUserFeedback::QtVersionSource); + feedbackProvider->addDataSource(new KUserFeedback::UsageTimeSource); + feedbackProvider->addDataSource(new KUserFeedback::OpenGLInfoSource); + feedbackProvider->addDataSource(new KUserFeedback::ScreenInfoSource); + feedbackProvider->addDataSource(new PanelCountSource(this)); + + { + auto plasmaConfig = KSharedConfig::openConfig(QStringLiteral("PlasmaUserFeedback")); + feedbackProvider->setTelemetryMode(KUserFeedback::Provider::TelemetryMode(plasmaConfig->group("Global").readEntry("FeedbackLevel", int(KUserFeedback::Provider::NoTelemetry)))); + } +#endif + //FIXME: this would change the runtime platform to a fixed one if available // but a different way to load platform specific components is needed beforehand // because if we import and use two different components plugin, the second time @@ -309,7 +375,6 @@ load(); } - QJsonObject dumpconfigGroupJS(const KConfigGroup &rootGroup) { QJsonObject result; @@ -402,7 +467,7 @@ || cont->location() == Plasma::Types::BottomEdge || cont->location() == Plasma::Types::LeftEdge || cont->location() == Plasma::Types::RightEdge) && - cont->pluginMetaData().pluginId() != QStringLiteral("org.kde.plasma.private.systemtray"); + cont->pluginMetaData().pluginId() != QLatin1String("org.kde.plasma.private.systemtray"); }; auto isDesktop = [] (Plasma::Containment *cont) { @@ -610,7 +675,7 @@ return; } - KSharedConfig::Ptr conf = KSharedConfig::openConfig(QStringLiteral("plasma-") + m_shell + QStringLiteral("-appletsrc"), KConfig::SimpleConfig); + KSharedConfig::Ptr conf = KSharedConfig::openConfig(QLatin1String("plasma-") + m_shell + QLatin1String("-appletsrc"), KConfig::SimpleConfig); m_lookAndFeelPackage.setPath(packageName); @@ -910,7 +975,7 @@ addOutput(screen); } - script = ShellManager::s_testModeLayout; + script = m_testModeLayout; if (script.isEmpty()) { script = m_lookAndFeelPackage.filePath("layouts", QString(shell() + "-layout.js").toLatin1()); @@ -996,6 +1061,11 @@ } QRegion ShellCorona::availableScreenRegion(int id) const +{ + return m_strutManager->availableScreenRegion(id); +} + +QRegion ShellCorona::_availableScreenRegion(int id) const { DesktopView* view = m_desktopViewforId.value(id); if (!view) { @@ -1016,6 +1086,11 @@ } QRect ShellCorona::availableScreenRect(int id) const +{ + return m_strutManager->availableScreenRect(id); +} + +QRect ShellCorona::_availableScreenRect(int id) const { DesktopView *view = m_desktopViewforId.value(id); if (!view) { @@ -1552,7 +1627,7 @@ // Checking whether the result we got is valid. Just in case. Q_ASSERT_X(!existingActivities.isEmpty(), "isEmpty", "There are no activities, and the service is running"); - Q_ASSERT_X(existingActivities[0] != QStringLiteral("00000000-0000-0000-0000-000000000000"), + Q_ASSERT_X(existingActivities[0] != QLatin1String("00000000-0000-0000-0000-000000000000"), "null uuid", "There is a nulluuid activity present"); // Killing the unassigned containments @@ -1733,7 +1808,7 @@ void ShellCorona::checkAddPanelAction(const QStringList &sycocaChanges) { - if (!sycocaChanges.isEmpty() && !sycocaChanges.contains(QStringLiteral("services"))) { + if (!sycocaChanges.isEmpty() && !sycocaChanges.contains(QLatin1String("services"))) { return; } @@ -1746,7 +1821,7 @@ auto filter = [](const KPluginMetaData &md) -> bool { - return md.value(QStringLiteral("NoDisplay")) != QStringLiteral("true") && KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-ContainmentCategories")).contains(QStringLiteral("panel")); + return md.value(QStringLiteral("NoDisplay")) != QLatin1String("true") && KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-ContainmentCategories")).contains(QLatin1String("panel")); }; QList templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); @@ -1777,15 +1852,15 @@ const KPluginInfo::List panelContainmentPlugins = Plasma::PluginLoader::listContainmentsOfType(QStringLiteral("Panel")); QMap > sorted; for (const KPluginInfo &plugin : panelContainmentPlugins) { - if (plugin.property(QStringLiteral("NoDisplay")).toString() == QStringLiteral("true")) { + if (plugin.property(QStringLiteral("NoDisplay")).toString() == QLatin1String("true")) { continue; } sorted.insert(plugin.name(), qMakePair(plugin, KPluginMetaData())); } auto filter = [](const KPluginMetaData &md) -> bool { - return md.value(QStringLiteral("NoDisplay")) != QStringLiteral("true") && KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-ContainmentCategories")).contains(QStringLiteral("panel")); + return md.value(QStringLiteral("NoDisplay")) != QLatin1String("true") && KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-ContainmentCategories")).contains(QLatin1String("panel")); }; const QList templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); for (auto tpl : templates) { diff --git a/shell/shellmanager.h b/shell/shellmanager.h deleted file mode 100644 --- a/shell/shellmanager.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2013 Ivan Cukic - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * or (at your option) any later version, as published by the Free - * Software Foundation - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef SHELLMANAGER_H -#define SHELLMANAGER_H - -#include - -namespace Plasma { - class Corona; -} - -/** - * ShellManager creates a ShellCorona instance and manages it. - * - * Shell manager loads "handlers" from QML files which suggests which shell - * corona should currently be active. - * For example switching between tablet and desktop shells on hardware changes. - * - * This class also provides crash handling. - */ - -class ShellManager: public QObject { - Q_OBJECT -public: - static ShellManager * instance(); - ~ShellManager() override; - - static bool s_standaloneOption; - static QString s_fixedShell; - static QString s_testModeLayout; - - Plasma::Corona* corona() const; - -protected Q_SLOTS: - void registerHandler(QObject * handler); - void deregisterHandler(QObject * handler); - -public Q_SLOTS: - void requestShellUpdate(); - void updateShell(); - -Q_SIGNALS: - void shellChanged(const QString & shell); - void glInitializationFailed(); - -private Q_SLOTS: - void loadHandlers(); - -private: - ShellManager(); - - class Private; - const QScopedPointer d; -}; - -#endif /* SHELLMANAGER_H */ - diff --git a/shell/shellmanager.cpp b/shell/shellmanager.cpp deleted file mode 100644 --- a/shell/shellmanager.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2013 Ivan Cukic - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * or (at your option) any later version, as published by the Free - * Software Foundation - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "shellmanager.h" - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -//#include -#include "shellcorona.h" -#include "config-workspace.h" - -#include -#include - -#include - -static const QStringList s_shellsDirs(QStandardPaths::locateAll(QStandardPaths::QStandardPaths::GenericDataLocation, - PLASMA_RELATIVE_DATA_INSTALL_DIR "/shells/", - QStandardPaths::LocateDirectory)); -static const QString s_shellLoaderPath = QStringLiteral("/contents/loader.qml"); - -bool ShellManager::s_standaloneOption = false; -QString ShellManager::s_fixedShell; -QString ShellManager::s_testModeLayout; - -// -// ShellManager -// - -class ShellManager::Private { -public: - Private() - : currentHandler(nullptr), - corona(nullptr) - { - shellUpdateDelay.setInterval(100); - shellUpdateDelay.setSingleShot(true); - } - - QList handlers; - QObject * currentHandler; - QTimer shellUpdateDelay; - ShellCorona * corona; -}; - -ShellManager::ShellManager() - : d(new Private()) -{ - //we have to ensure this is executed after QCoreApplication::exec() - QMetaObject::invokeMethod(this, "loadHandlers", Qt::QueuedConnection); - connect(&d->shellUpdateDelay, &QTimer::timeout, - this, &ShellManager::updateShell); -} - -ShellManager::~ShellManager() -{ - // if (d->currentHandler) - // d->currentHandler->unload(); -} - -void ShellManager::loadHandlers() -{ - //this should be executed one single time in the app life cycle - Q_ASSERT(!d->corona); - - d->corona = new ShellCorona(this); - connect(d->corona, &ShellCorona::glInitializationFailed, - this, &ShellManager::glInitializationFailed); - - connect( - this, &ShellManager::shellChanged, - d->corona, &ShellCorona::setShell - ); - - for (const QString &shellsDir : qAsConst(s_shellsDirs)) { - const auto dirs = QDir(shellsDir).entryList(QDir::Dirs | QDir::NoDotAndDotDot); - for (const auto &dir : dirs) { - const QString qmlFile = shellsDir + dir + s_shellLoaderPath; - // qDebug() << "Making a new instance of " << qmlFile; - - //this shell is not valid, ignore it - if (!QFileInfo::exists(qmlFile)) { - continue; - } - - auto *handlerContainer = new KDeclarative::QmlObjectSharedEngine(this); - auto handler = handlerContainer->createObjectFromSource(QUrl::fromLocalFile(qmlFile), nullptr, { - { "pluginName", dir }, - - // This property is useful for shells to launch themselves in some specific sessions - // For example mediacenter shell can be launched when in plasma-mediacenter session - { "currentSession", QString::fromUtf8(qgetenv("DESKTOP_SESSION")) } - }); - - if (handler) { - registerHandler(handler); - } - } - } - - updateShell(); -} - -void ShellManager::registerHandler(QObject * handler) -{ - // qDebug() << "We got the handler: " << handler->property("shell").toString(); - - connect( - handler, &QObject::destroyed, - this, &ShellManager::deregisterHandler - ); - - connect( - handler, SIGNAL(willingChanged()), - this, SLOT(requestShellUpdate()) - ); - - connect( - handler, SIGNAL(priorityChanged()), - this, SLOT(requestShellUpdate()) - ); - - d->handlers.push_back(handler); -} - -void ShellManager::deregisterHandler(QObject * handler) -{ - const int removed = d->handlers.removeAll(handler); - if (removed > 0) { - handler->disconnect(this); - } - - if (d->currentHandler == handler) { - d->currentHandler = nullptr; - updateShell(); - } - handler->deleteLater(); -} - -void ShellManager::requestShellUpdate() -{ - d->shellUpdateDelay.start(); -} - -void ShellManager::updateShell() -{ - d->shellUpdateDelay.stop(); - - if (d->handlers.isEmpty()) { - KMessageBox::error(nullptr, //wID, but we don't have a window yet - i18nc("Fatal error message body","All shell packages missing.\nThis is an installation issue, please contact your distribution"), - i18nc("Fatal error message title", "Plasma Cannot Start")); - qCritical("We have no shell handlers installed"); - QCoreApplication::exit(-1); - } - - QObject *handler = nullptr; - - if (!s_fixedShell.isEmpty()) { - QList::const_iterator it = std::find_if (d->handlers.cbegin(), d->handlers.cend(), [=] (QObject *handler) { - return handler->property("pluginName").toString() == s_fixedShell; - }); - if (it != d->handlers.cend()) { - handler = *it; - } else { - KMessageBox::error(nullptr, - i18nc("Fatal error message body", "Shell package %1 cannot be found", s_fixedShell), - i18nc("Fatal error message title", "Plasma Cannot Start")); - qCritical("Unable to find the shell plugin '%s'", qPrintable(s_fixedShell)); - QCoreApplication::exit(-1); - } - } else { - // Finding the handler that has the priority closest to zero. - // We will return a handler even if there are no willing ones. - handler =* std::min_element(d->handlers.cbegin(), d->handlers.cend(), - [] (QObject * left, QObject * right) - { - auto willing = [] (QObject * handler) - { - return handler->property("willing").toBool(); - }; - - auto priority = [] (QObject * handler) - { - return handler->property("priority").toInt(); - }; - - return - // If one is willing and the other is not, - // return it - it has the priority - willing(left) && !willing(right) ? true : - !willing(left) && willing(right) ? false : - // otherwise just compare the priorities - priority(left) < priority(right); - } - ); - } - - if (handler == d->currentHandler) return; - - // Activating the new handler and killing the old one - if (d->currentHandler) { - d->currentHandler->setProperty("loaded", false); - } - - // handler will never be null, unless there is no shells - // available on the system, which is definitely not something - // we want to support :) - d->currentHandler = handler; - d->currentHandler->setProperty("loaded", true); - - emit shellChanged(d->currentHandler->property("shell").toString()); -} - -ShellManager * ShellManager::instance() -{ - static ShellManager* manager = nullptr; - if (!manager) { - manager = new ShellManager; - } - return manager; -} - -Plasma::Corona* ShellManager::corona() const -{ - return d->corona; -} diff --git a/shell/strutmanager.h b/shell/strutmanager.h new file mode 100644 --- /dev/null +++ b/shell/strutmanager.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019 David Edmundson + * Copyright 2019 Tranter Madi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef STRUTMANAGER_H +#define STRUTMANAGER_H + +#include +#include + +class QDBusServiceWatcher; +class ShellCorona; + +class StrutManager : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface","org.kde.PlasmaShell.StrutManager") + + public: + explicit StrutManager(ShellCorona *plasmashellCorona); + + QRect availableScreenRect(int id) const; + QRegion availableScreenRegion(int id) const; + + public Q_SLOTS: + void setAvailableScreenRect(const QString &service, const QString &screenName, const QRect &rect); + void setAvailableScreenRegion(const QString &service, const QString &screenName, const QList &rects); + + private: + ShellCorona *m_plasmashellCorona; + + QDBusServiceWatcher *m_serviceWatcher; + bool addWatchedService(const QString &service); + + QHash > m_availableScreenRects; + QHash > m_availableScreenRegions; +}; + +#endif diff --git a/shell/strutmanager.cpp b/shell/strutmanager.cpp new file mode 100644 --- /dev/null +++ b/shell/strutmanager.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2019 David Edmundson + * Copyright 2019 Tranter Madi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "strutmanager.h" +#include "shellcorona.h" +#include "screenpool.h" + +#include +#include +#include +#include + +StrutManager::StrutManager(ShellCorona *plasmashellCorona) : QObject(plasmashellCorona), + m_plasmashellCorona(plasmashellCorona), + m_serviceWatcher(new QDBusServiceWatcher(this)) +{ + qDBusRegisterMetaType>(); + + QDBusConnection dbus = QDBusConnection::sessionBus(); + dbus.registerObject("/StrutManager", this, QDBusConnection::ExportAllSlots); + m_serviceWatcher->setConnection(dbus); + + connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, [=](const QString &service) { + m_availableScreenRects.remove(service); + m_availableScreenRegions.remove(service); + m_serviceWatcher->removeWatchedService(service); + + emit m_plasmashellCorona->availableScreenRectChanged(); + }); +} + +QRect StrutManager::availableScreenRect(int id) const +{ + QRect r = m_plasmashellCorona->_availableScreenRect(id); + QHash service; + foreach (service, m_availableScreenRects) { + if (!service.value(id).isNull()) { + r &= service[id]; + } + } + return r; +} + +QRegion StrutManager::availableScreenRegion(int id) const +{ + QRegion r = m_plasmashellCorona->_availableScreenRegion(id); + QHash service; + foreach (service, m_availableScreenRegions) { + if (!service.value(id).isNull()) { + r &= service[id]; + } + } + return r; +} + +void StrutManager::setAvailableScreenRect(const QString &service, const QString &screenName, const QRect &rect) { + int id = m_plasmashellCorona->screenPool()->id(screenName); + if (id == -1 || m_availableScreenRects.value(service).value(id) == rect || !addWatchedService(service)) { + return; + } + m_availableScreenRects[service][id] = rect; + emit m_plasmashellCorona->availableScreenRectChanged(); +} + +void StrutManager::setAvailableScreenRegion(const QString &service, const QString &screenName, const QList &rects) { + int id = m_plasmashellCorona->screenPool()->id(screenName); + QRegion region; + foreach(QRect rect, rects) { + region += rect; + } + + if (id == -1 || m_availableScreenRegions.value(service).value(id) == region || !addWatchedService(service)) { + return; + } + m_availableScreenRegions[service][id] = region; + emit m_plasmashellCorona->availableScreenRegionChanged(); +} + +bool StrutManager::addWatchedService(const QString &service) { + if (!m_serviceWatcher->watchedServices().contains(service)) { + if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(service)) { + return false; + } + m_serviceWatcher->addWatchedService(service); + } + return true; +} diff --git a/shell/tests/plasma/shells/org.kde.plasmashelltest/metadata.desktop b/shell/tests/plasma/shells/org.kde.plasmashelltest/metadata.desktop new file mode 100644 --- /dev/null +++ b/shell/tests/plasma/shells/org.kde.plasmashelltest/metadata.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Comment=TEST ALL THE THINGS! ;) +Name=TestShell +Type=Service +Icon=user-desktop + +X-KDE-ServiceTypes=Plasma/Shell +X-KDE-PluginInfo-Name=org.kde.plasmashelltest +X-Plasma-MainScript=layout.js diff --git a/solidautoeject/solidautoeject.desktop b/solidautoeject/solidautoeject.desktop --- a/solidautoeject/solidautoeject.desktop +++ b/solidautoeject/solidautoeject.desktop @@ -36,7 +36,7 @@ Name[km]=កម្មវិធីច្រាន​ដ្រាយ​ចេញ Name[kn]=ಡ್ರೈವ್ ಹೊರತಳ್ಳುಕ Name[ko]=장치 꺼내기 -Name[lt]=Laikmenų išstumiklis +Name[lt]=Diskų išstūmimo įrankis Name[lv]=Disku izgrūdējs Name[ml]=ഡ്രൈവ് പുറത്തെടുക്കുവാന്‍ Name[mr]=ड्राइव्ह बाहेर काढा @@ -58,7 +58,6 @@ Name[sr@ijekavianlatin]=Izbacivač jedinica Name[sr@latin]=Izbacivač jedinica Name[sv]=Utmatning av enheter -Name[tg]=Ҷудокунандаи дастгоҳ Name[th]=ตัวดันถาดไดรฟ์ Name[tr]=Sürücü Çıkarıcı Name[ug]=قوزغاتقۇچ قاڭقىتقۇچ @@ -73,7 +72,7 @@ Comment[bg]=Автоматично освобождаване на устройствата при натискане на бутона за изваждане Comment[bs]=Automatsko oslobađanje jedinica kada im se prisne dugme za izbacivanje Comment[ca]=Allibera unitats automàticament quan es prem el seu botó d'expulsió -Comment[ca@valencia]=Allibera unitats automàticament quan es prem el seu botó d'expulsió +Comment[ca@valencia]=Allibera unitats automàticament quan es prem el botó d'expulsió Comment[cs]=Umožňuje automaticky uvolnit mechaniku při stisknutí tlačítka vysunutí Comment[da]=Frigiver automatisk drev når der trykkes på deres skub ud-knap Comment[de]=Automatisches Lösen der Laufwerkseinbindung bei Betätigung des Auswurfknopfes @@ -101,7 +100,7 @@ Comment[km]=ចេញ​​ផ្សាយ​ដ្រាយ​ដោយ​ស្វ័យ​ប្រវត្តិ ​នៅពេល​ចុច​ប៊ូតុង​ច្រានចេញ​របស់​វា Comment[kn]=ಹೊರತಳ್ಳು ಗುಂಡಿಯನ್ನು ಒತ್ತಿದಾಗ ಡ್ರೈವ್‌ಗಳನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಬಿಡುಗಡೆಗೊಳಿಸಲು ಅನುವು ಮಾಡುತ್ತದೆ Comment[ko]=꺼내기 단추를 눌렀을 때 자동으로 드라이브 꺼내기 -Comment[lt]=Automatiškai atjungia įrenginius paspaudus išmetimo mygtuką +Comment[lt]=Automatiškai atjungia įrenginius paspaudus išstūmimo mygtuką Comment[lv]=Ļauj automātiski izgrūst diskus pēc izgrūšanas pogas nospiešanas Comment[ml]=പുറത്തെടുക്കുവാനുള്ള ബട്ടണ്‍ അമര്‍ത്തുബോള്‍ ഡ്രൈവുകള്‍ സ്വയം പുറത്തെടുക്കുന്നു Comment[mr]=जेव्हा ड्राइव्हचे इजेक्ट बटन दाबले जाईल तेव्हा स्वयंचलितरित्या ड्राइव्ह सोडतो @@ -123,7 +122,6 @@ Comment[sr@ijekavianlatin]=Automatsko oslobađanje jedinica kada im se pritisne dugme za izbacivanje Comment[sr@latin]=Automatsko oslobađanje jedinica kada im se pritisne dugme za izbacivanje Comment[sv]=Frisläpper automatiskt enheter vid tryck på deras utmatningsknapp -Comment[tg]=Дастгоҳҳоро ба худкор ҷудо мекунад вақте, ки шумо тугмаҳои ҷудокунии онро зер мекунед Comment[th]=เปิดถาดไดรฟ์ให้อัตโนมัติเมื่อมีการกดปุ่มเอาแผ่นสื่อออก Comment[tr]=Çıkarma düğmesine basıldığında sürücüleri otomatik olarak ayırır Comment[ug]=قاڭقىت توپچا بېسىلغاندا قوزغاتقۇچ مەنبەسىنى ئۆزلۈكىدىن قويۇپ بېرىدۇ diff --git a/soliduiserver/soliduiserver.cpp b/soliduiserver/soliduiserver.cpp --- a/soliduiserver/soliduiserver.cpp +++ b/soliduiserver/soliduiserver.cpp @@ -72,7 +72,7 @@ label+= device.product(); dialog->setPrompt(i18n("'%1' needs a password to be accessed. Please enter a password.", label)); - dialog->setPixmap(QIcon::fromTheme(device.icon()).pixmap(64, 64)); + dialog->setIcon(QIcon::fromTheme(device.icon())); dialog->setProperty("soliduiserver.udi", udi); dialog->setProperty("soliduiserver.returnService", returnService); dialog->setProperty("soliduiserver.returnObject", returnObject); @@ -155,7 +155,8 @@ Q_UNUSED(appId); // Code borrowed from kwalletd - KWindowSystem::setMainWindow(dialog, wId); // correct, set dialog parent + dialog->setAttribute(Qt::WA_NativeWindow, true); + KWindowSystem::setMainWindow(dialog->windowHandle(), wId); // correct, set dialog parent #if HAVE_X11 if (modal) { diff --git a/soliduiserver/soliduiserver.desktop b/soliduiserver/soliduiserver.desktop --- a/soliduiserver/soliduiserver.desktop +++ b/soliduiserver/soliduiserver.desktop @@ -6,6 +6,7 @@ X-KDE-Kded-load-on-demand=true Name=Hardware Detection Name[ar]=اكتشاف العتاد +Name[ast]=Deteición de hardware Name[bs]=Detekcija hardvera Name[ca]=Detecció de maquinari Name[ca@valencia]=Detecció de maquinari @@ -19,7 +20,7 @@ Name[eu]=Hardware-detekzioa Name[fi]=Laitteiston tunnistus Name[fr]=Détection du matériel -Name[gl]=Detección do hardware +Name[gl]=Detección de soporte físico Name[he]=זיהוי חומרה Name[hu]=Hardverfelismerés Name[ia]=Relevamento de Hardware @@ -54,7 +55,7 @@ Comment[ar]=يوفّر واجهة مستخدِم لأحداث العتاد Comment[bs]=Omogućava korisničko sučelje za hardverske događaje Comment[ca]=Proporciona una interfície d'usuari pels esdeveniments del maquinari -Comment[ca@valencia]=Proporciona una interfície d'usuari pels esdeveniments del maquinari +Comment[ca@valencia]=Proporciona una interfície d'usuari per als esdeveniments del maquinari Comment[cs]=Poskytuje uživatelské rozhraní pro hardwarové události Comment[da]=Giver en brugerflade til hardware-hændelser Comment[de]=Stellt eine Benutzeroberfläche für Hardwareereignisse zur Verfügung @@ -65,16 +66,16 @@ Comment[eu]=Hardware gertakarientzako erabiltzaile interfaze bat hornitzen du Comment[fi]=Tarjoaa käyttöliittymän laitteistotapahtumille Comment[fr]=Fournit une interface utilisateur pour les évènements matériels -Comment[gl]=Fornece unha interface de usuario para os eventos do hardware +Comment[gl]=Fornece unha interface de usuario para os eventos de soporte físico Comment[he]=מספק ממשק משתמש לארועי חומרה Comment[hu]=Felhasználói felületet biztosít a hardvereseményekhez Comment[ia]=Il forni un interface de usator pro eventos hardware Comment[id]=Menyediakan antarmuka pengguna untuk peristiwa perangkat keras Comment[is]=Sér um grafískt notandaviðmót vegna vélbúnaðartengdra atburða Comment[it]=Fornisce un'interfaccia utente per gli eventi hardware Comment[ja]=ハードウェアイベントのためのユーザインターフェースを提供します Comment[ko]=하드웨어 이벤트 사용자 인터페이스 제공 -Comment[lt]=Suteikia naudotojo sąsają su įvykiais techninėje įrangoje +Comment[lt]=Suteikia naudotojo sąsają aparatinės įrangos įvykiams Comment[nb]=Et brukergrensesnitt for maskinvarehendinger Comment[nds]=Stellt en Bruker-Koppelsteed för Hardware-Begeefnissen praat. Comment[nl]=Levert een gebruikersinterface voor hardware gebeurtenissen diff --git a/startkde/kcminit/CMakeLists.txt b/startkde/kcminit/CMakeLists.txt --- a/startkde/kcminit/CMakeLists.txt +++ b/startkde/kcminit/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(XCB OPTIONAL_COMPONENTS XCB) set_package_properties(XCB PROPERTIES DESCRIPTION "Xcb libraries" - URL "http://www.x.org" + URL "https://www.x.org" TYPE OPTIONAL PURPOSE "Required for enabling special X11 multihead mode") configure_file(config-xcb.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-xcb.h) diff --git a/startkde/kcminit/main.cpp b/startkde/kcminit/main.cpp --- a/startkde/kcminit/main.cpp +++ b/startkde/kcminit/main.cpp @@ -79,17 +79,27 @@ else kcminit = KCMINIT_PREFIX + libName; + QString path = KPluginLoader::findPlugin(libName); + if (path.isEmpty()) { + path = KPluginLoader::findPlugin(QStringLiteral("kcms/") + libName); + } + + if (path.isEmpty()) { + qWarning() << "Module" << libName << "was not found"; + return false; + } + // get the kcminit_ function - QFunctionPointer init = QLibrary::resolve(KPluginLoader::findPlugin(libName), kcminit.toUtf8().constData()); - if (init) { - // initialize the module - qDebug() << "Initializing " << libName << ": " << kcminit; - init(); - return true; - } else { - qWarning() << "Module" << libName << "was not found or does not actually have a kcminit function"; + QFunctionPointer init = QLibrary::resolve(path, kcminit.toUtf8().constData()); + if (!init) { + qWarning() << "Module" << libName << "does not actually have a kcminit function"; + return false; } - return false; + + // initialize the module + qDebug() << "Initializing " << libName << ": " << kcminit; + init(); + return true; } void KCMInit::runModules( int phase ) diff --git a/startkde/plasma-session/CMakeLists.txt b/startkde/plasma-session/CMakeLists.txt --- a/startkde/plasma-session/CMakeLists.txt +++ b/startkde/plasma-session/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(plasma-autostart-list) set(plasma_session_SRCS main.cpp diff --git a/startkde/plasma-session/autostart.h b/startkde/plasma-session/autostart.h --- a/startkde/plasma-session/autostart.h +++ b/startkde/plasma-session/autostart.h @@ -23,15 +23,21 @@ #include #include -class AutoStartItem; +class AutoStartItem +{ +public: + QString name; + QString service; + QString startAfter; + int phase; +}; class AutoStart { public: AutoStart(); ~AutoStart(); - void loadAutoStartList(); QString startService(); void setPhase(int phase); void setPhaseDone(); @@ -43,8 +49,10 @@ { return m_phasedone; } + QVector startList() const; private: + void loadAutoStartList(); QVector m_startList; QStringList m_started; int m_phase; diff --git a/startkde/plasma-session/autostart.cpp b/startkde/plasma-session/autostart.cpp --- a/startkde/plasma-session/autostart.cpp +++ b/startkde/plasma-session/autostart.cpp @@ -22,20 +22,12 @@ #include #include #include - -class AutoStartItem -{ -public: - QString name; - QString service; - QString startAfter; - int phase; -}; -Q_DECLARE_TYPEINFO(AutoStartItem, Q_MOVABLE_TYPE); +#include AutoStart::AutoStart() : m_phase(-1), m_phasedone(false) { + loadAutoStartList(); } AutoStart::~AutoStart() @@ -64,7 +56,7 @@ } i = path.lastIndexOf(QLatin1Char('.')); if (i >= 0) { - path = path.left(i); + path.truncate(i); } return path; } @@ -75,13 +67,14 @@ // XDG autostart dirs // Make unique list of relative paths - QStringList files; + QHash files; QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("autostart"), QStandardPaths::LocateDirectory); Q_FOREACH (const QString &dir, dirs) { - const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.desktop")); + const QDir d(dir); + const QStringList fileNames = d.entryList(QStringList() << QStringLiteral("*.desktop")); Q_FOREACH (const QString &file, fileNames) { if (!files.contains(file)) { - files.append(file); + files.insert(file, d.absoluteFilePath(file)); } } } @@ -92,10 +85,9 @@ continue; } - const auto file = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("autostart/") + *it); AutoStartItem item; - item.name = extractName(file); - item.service = file; + item.service = *it; + item.name = extractName(it.key()); item.startAfter = config.startAfter(); item.phase = qMax(KAutostart::BaseDesktop, config.startPhase()); m_startList.append(item); @@ -154,3 +146,13 @@ return QString(); } + +QVector AutoStart::startList() const +{ + QVector ret; + for (const auto &asi : m_startList) { + if (asi.phase == m_phase) + ret << asi; + } + return ret; +} diff --git a/startkde/plasma-session/plasma-autostart-list/CMakeLists.txt b/startkde/plasma-session/plasma-autostart-list/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/startkde/plasma-session/plasma-autostart-list/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(plasma-autostart-list main.cpp ../autostart.cpp) +target_link_libraries(plasma-autostart-list KF5::Service) diff --git a/startkde/plasma-session/plasma-autostart-list/main.cpp b/startkde/plasma-session/plasma-autostart-list/main.cpp new file mode 100644 --- /dev/null +++ b/startkde/plasma-session/plasma-autostart-list/main.cpp @@ -0,0 +1,50 @@ +/* This file is part of the KDE project + Copyright (C) 2019 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include "../autostart.h" + +int main(int argc, char** argv) +{ + QCoreApplication app(argc, argv); + AutoStart as; + + QTextStream cout(stdout); + auto printPhase = [&cout, &as] (int phase) -> bool { + AutoStart asN(as); + asN.setPhase(phase); + cout << "phase: " << phase << '\n'; + bool foundThings = true; + for (auto asi : asN.startList()) { + foundThings = false; + cout << "- " << asi.name << ' ' << asi.service; + if (!asi.startAfter.isEmpty()) + cout << ", startAfter:" << asi.startAfter; + cout << '\n'; + } + cout << '\n'; + return !foundThings; + }; + + printPhase(0); + printPhase(1); + printPhase(2); + return 0; +} diff --git a/startkde/plasma-session/shutdown.cpp b/startkde/plasma-session/shutdown.cpp --- a/startkde/plasma-session/shutdown.cpp +++ b/startkde/plasma-session/shutdown.cpp @@ -82,7 +82,7 @@ const QStringList entries = dir.entryList(QDir::Files); foreach (const QString &file, entries) { // Don't execute backup files - if (!file.endsWith(QLatin1Char('~')) && !file.endsWith(QStringLiteral(".bak")) && + if (!file.endsWith(QLatin1Char('~')) && !file.endsWith(QLatin1String(".bak")) && (file[0] != QLatin1Char('%') || !file.endsWith(QLatin1Char('%'))) && (file[0] != QLatin1Char('#') || !file.endsWith(QLatin1Char('#')))) { diff --git a/startkde/plasma-session/startup.h b/startkde/plasma-session/startup.h --- a/startkde/plasma-session/startup.h +++ b/startkde/plasma-session/startup.h @@ -29,9 +29,6 @@ #include "autostart.h" -class KSMServer; -class KCompositeJob; - class Startup : public QObject { Q_OBJECT @@ -77,7 +74,7 @@ { Q_OBJECT public: - AutoStartAppsJob(int phase); + AutoStartAppsJob(const AutoStart &autoStart, int phase); void start() override; private: AutoStart m_autoStart; diff --git a/startkde/plasma-session/startup.cpp b/startkde/plasma-session/startup.cpp --- a/startkde/plasma-session/startup.cpp +++ b/startkde/plasma-session/startup.cpp @@ -66,8 +66,9 @@ { Q_OBJECT public: - Phase(QObject *parent): - KCompositeJob(parent) + Phase(const AutoStart &autostart, QObject *parent) + : KCompositeJob(parent) + , m_autostart(autostart) {} bool addSubjob(KJob *job) override { @@ -82,17 +83,20 @@ emitResult(); } } + +protected: + const AutoStart m_autostart; }; class StartupPhase0: public Phase { Q_OBJECT public: - StartupPhase0(QObject *parent) : Phase(parent) + StartupPhase0(const AutoStart& autostart, QObject *parent) : Phase(autostart, parent) {} void start() override { qCDebug(PLASMA_SESSION) << "Phase 0"; - addSubjob(new AutoStartAppsJob(0)); + addSubjob(new AutoStartAppsJob(m_autostart, 0)); addSubjob(new KCMInitJob(1)); addSubjob(new SleepJob()); } @@ -102,26 +106,26 @@ { Q_OBJECT public: - StartupPhase1(QObject *parent) : Phase(parent) + StartupPhase1(const AutoStart& autostart, QObject *parent) : Phase(autostart, parent) {} void start() override { qCDebug(PLASMA_SESSION) << "Phase 1"; - addSubjob(new AutoStartAppsJob(1)); + addSubjob(new AutoStartAppsJob(m_autostart, 1)); } }; class StartupPhase2: public Phase { Q_OBJECT public: - StartupPhase2(QObject *parent) : Phase(parent) + StartupPhase2(const AutoStart& autostart, QObject *parent) : Phase(autostart, parent) {} void runUserAutostart(); bool migrateKDE4Autostart(const QString &folder); void start() override { qCDebug(PLASMA_SESSION) << "Phase 2"; - addSubjob(new AutoStartAppsJob(2)); + addSubjob(new AutoStartAppsJob(m_autostart, 2)); addSubjob(new KDEDInitJob()); addSubjob(new KCMInitJob(2)); runUserAutostart(); @@ -151,7 +155,7 @@ QObject parent; KNotifyConfig notifyConfig(QStringLiteral("plasma_workspace"), QList< QPair >(), QStringLiteral("startkde")); const QString action = notifyConfig.readEntry(QStringLiteral("Action")); - if (action.isEmpty() || !action.split(QLatin1Char('|')).contains(QStringLiteral("Sound"))) { + if (action.isEmpty() || !action.split(QLatin1Char('|')).contains(QLatin1String("Sound"))) { // no startup sound configured return; } @@ -200,9 +204,11 @@ QDBusConnection::sessionBus().registerObject(QStringLiteral("/Startup"), QStringLiteral("org.kde.Startup"), this); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Startup")); - auto phase0 = new StartupPhase0(this); - auto phase1 = new StartupPhase1(this); - auto phase2 = new StartupPhase2(this); + const AutoStart autostart; + + auto phase0 = new StartupPhase0(autostart, this); + auto phase1 = new StartupPhase1(autostart, this); + auto phase2 = new StartupPhase2(autostart, this); auto restoreSession = new RestoreSessionJob(); // this includes starting kwin (currently) @@ -319,7 +325,7 @@ const QStringList entries = dir.entryList(QDir::Files); foreach (const QString &file, entries) { // Don't execute backup files - if (!file.endsWith(QLatin1Char('~')) && !file.endsWith(QStringLiteral(".bak")) && + if (!file.endsWith(QLatin1Char('~')) && !file.endsWith(QLatin1String(".bak")) && (file[0] != QLatin1Char('%') || !file.endsWith(QLatin1Char('%'))) && (file[0] != QLatin1Char('#') || !file.endsWith(QLatin1Char('#')))) { @@ -373,9 +379,9 @@ return true; } -AutoStartAppsJob::AutoStartAppsJob(int phase) +AutoStartAppsJob::AutoStartAppsJob(const AutoStart & autostart, int phase) + : m_autoStart(autostart) { - m_autoStart.loadAutoStartList(); //FIXME, share this between jobs m_autoStart.setPhase(phase); } @@ -414,9 +420,7 @@ m_args(args) { auto watcher = new QDBusServiceWatcher(serviceId, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this); - connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, [=]() { - emitResult(); - }); + connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &StartServiceJob::emitResult); } void StartServiceJob::start() diff --git a/startkde/plasma-sourceenv.sh b/startkde/plasma-sourceenv.sh --- a/startkde/plasma-sourceenv.sh +++ b/startkde/plasma-sourceenv.sh @@ -3,4 +3,5 @@ . $i >/dev/null done -env +# env may not support -0, fall back to GNU env +env -0 2>/dev/null || genv -0 diff --git a/startkde/startplasma-wayland.cpp b/startkde/startplasma-wayland.cpp --- a/startkde/startplasma-wayland.cpp +++ b/startkde/startplasma-wayland.cpp @@ -23,7 +23,7 @@ #include #include -int main(int /*argc*/, char** /*argv*/) +int main(int argc, char** argv) { createConfigDirectory(); setupCursor(true); @@ -82,7 +82,16 @@ return 1; } - runSync(QStringLiteral(KWIN_WAYLAND_BIN_PATH), { QStringLiteral("--xwayland"), QStringLiteral("--libinput"), QStringLiteral("--exit-with-session=" CMAKE_INSTALL_FULL_LIBEXECDIR "/startplasma-waylandsession") }); + QStringList args; + if (argc > 1) { + args.reserve(argc); + for (int i = 1; i < argc; ++i) { + args << QString::fromLocal8Bit(argv[i]); + } + } else { + args = QStringList { QStringLiteral("--xwayland"), QStringLiteral("--libinput"), QStringLiteral("--exit-with-session=" CMAKE_INSTALL_FULL_LIBEXECDIR "/startplasma-waylandsession") }; + } + runSync(QStringLiteral(KWIN_WAYLAND_BIN_PATH), args); out << "startplasmacompositor: Shutting down...\n"; cleanupPlasmaEnvironment(); diff --git a/startkde/startplasma.cpp b/startkde/startplasma.cpp --- a/startkde/startplasma.cpp +++ b/startkde/startplasma.cpp @@ -91,7 +91,7 @@ p.waitForFinished(-1); const auto fullEnv = p.readAllStandardOutput(); - auto envs = fullEnv.split('\n'); + auto envs = fullEnv.split('\0'); for (auto &env: envs) { if (env.startsWith("_=") || env.startsWith("SHLVL")) @@ -117,19 +117,31 @@ void runStartupConfig() { - const QString configDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + //export LC_* variables set by kcmshell5 formats into environment + //so it can be picked up by QLocale and friends. + KConfig config(QStringLiteral("plasma-localerc")); + KConfigGroup formatsConfig = KConfigGroup(&config, "Formats"); + + const auto lcValues = { + "LANG", "LC_NUMERIC", "LC_MONETARY", "LC_MEASUREMENT", "LC_COLLATE", "LC_CTYPE" + }; + for (auto lc : lcValues) { + const QString value = formatsConfig.readEntry(lc, QString()); + if (!value.isEmpty()) { + qputenv(lc, value.toUtf8()); + } + } - const QString localerc(configDir + QLatin1String("/plasma-localerc")); - if (!QFile::exists(localerc)) { - QFile f(localerc); - f.open(QFile::WriteOnly); - f.write("[Formats]\n" - "LANG=" + qgetenv("LANG") + '\n'); + KConfigGroup languageConfig = KConfigGroup(&config, "Translations"); + const QString value = languageConfig.readEntry("LANGUAGE", QString()); + if (!value.isEmpty()) { + qputenv("LANGUAGE", value.toUtf8()); } - //export LC_* variables set by kcmshell5 formats into environment - //so it can be picked up by QLocale and friends. - sourceFiles({configDir + QStringLiteral("/plasma-locale-settings.sh")}); + if (!formatsConfig.hasKey("LANG") && !qEnvironmentVariableIsEmpty("LANG")) { + formatsConfig.writeEntry("LANG", qgetenv("LANG")); + formatsConfig.sync(); + } } void setupCursor(bool wayland) @@ -148,7 +160,7 @@ } //TODO: consider linking directly - const int applyMouseStatus = wayland ? 0 : runSync(QStringLiteral("kapplymousetheme"), { QStringLiteral("kcminputrc_mouse_cursortheme"), QStringLiteral("kcminputrc_mouse_cursorsize") }); + const int applyMouseStatus = wayland ? 0 : runSync(QStringLiteral("kapplymousetheme"), { kcminputrc_mouse_cursortheme, kcminputrc_mouse_cursorsize }); if (applyMouseStatus == 10) { qputenv("XCURSOR_THEME", "breeze_cursors"); } else if (!kcminputrc_mouse_cursortheme.isEmpty()) { @@ -184,14 +196,6 @@ } } sourceFiles(scripts); - - // Make sure that the KDE prefix is first in XDG_DATA_DIRS and that it's set at all. - // The spec allows XDG_DATA_DIRS to be not set, but X session startup scripts tend - // to set it to a list of paths *not* including the KDE prefix if it's not /usr or - // /usr/local. - if (!qEnvironmentVariableIsSet("XDG_DATA_DIRS")) { - qputenv("XDG_DATA_DIRS", KDE_INSTALL_FULL_DATAROOTDIR ":/usr/share:/usr/local/share"); - } } @@ -218,9 +222,11 @@ void setupPlasmaEnvironment() { +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) //Manually disable auto scaling because we are scaling above //otherwise apps that manually opt in for high DPI get auto scaled by the developer AND manually scaled by us qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0"); +#endif qputenv("KDE_FULL_SESSION", "true"); qputenv("KDE_SESSION_VERSION", "5"); @@ -358,6 +364,9 @@ if (wayland) { ksmserverOptions << QStringLiteral("--no-lockscreen"); } else { + if (qEnvironmentVariableIsSet("KDEWM")) { + ksmserverOptions << QStringLiteral("--windowmanager") << qEnvironmentVariable("KDEWM"); + } if (desktopLockedAtStart) { ksmserverOptions << QStringLiteral("--lockscreen"); } diff --git a/statusnotifierwatcher/statusnotifierwatcher.cpp b/statusnotifierwatcher/statusnotifierwatcher.cpp --- a/statusnotifierwatcher/statusnotifierwatcher.cpp +++ b/statusnotifierwatcher/statusnotifierwatcher.cpp @@ -111,7 +111,7 @@ void StatusNotifierWatcher::RegisterStatusNotifierHost(const QString &service) { - if (service.contains(QStringLiteral("org.kde.StatusNotifierHost-")) && + if (service.contains(QLatin1String("org.kde.StatusNotifierHost-")) && QDBusConnection::sessionBus().interface()->isServiceRegistered(service).value() && !m_statusNotifierHostServices.contains(service)) { qDebug()<<"Registering"< #include #include +#include #include #include @@ -165,7 +166,7 @@ // package (could have been symlinked) and we should work // with the package (which can already be present) rather // than just one file from it - int contentsIndex = file.indexOf(QStringLiteral("contents")); + int contentsIndex = file.indexOf(QLatin1String("contents")); // FIXME: additionally check for metadata.desktop being present // which would confirm a package but might be slowing things @@ -236,7 +237,7 @@ // packages will end with a '/', but the path passed in may not QString package = m_packages[i].path(); if (package.at(package.length() - 1) == QChar::fromLatin1('/')) { - package.truncate(package.length() - 1); + package.chop(1); } //remove eventual file:/// const QString filteredPath = QUrl(path).path(); @@ -518,7 +519,7 @@ } } - s_suffixes = suffixes.toList(); + s_suffixes = suffixes.values(); } return s_suffixes; @@ -529,12 +530,12 @@ // Despite its name, suffixes() returns a list of glob patterns. // Therefore the file suffix check needs to include the "*." prefix. const QStringList &globPatterns = suffixes(); - return globPatterns.contains(QStringLiteral("*.") + suffix.toLower()); + return globPatterns.contains(QLatin1String("*.") + suffix.toLower()); } void BackgroundFinder::run() { - QTime t; + QElapsedTimer t; t.start(); QStringList papersFound; diff --git a/wallpapers/image/image.h b/wallpapers/image/image.h --- a/wallpapers/image/image.h +++ b/wallpapers/image/image.h @@ -3,6 +3,7 @@ * Copyright 2008 by Petri Damsten * * Copyright 2014 Sebastian Kügler * * Copyright 2015 Kai Uwe Broulik * + * Copyright 2019 David Redondo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -24,6 +25,7 @@ #ifndef IMAGE_HEADER #define IMAGE_HEADER + #include #include #include @@ -48,16 +50,18 @@ class BackgroundListModel; class SlideModel; +class SlideFilterModel; class Image : public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(RenderingMode renderingMode READ renderingMode WRITE setRenderingMode NOTIFY renderingModeChanged) + Q_PROPERTY(SlideshowMode slideshowMode READ slideshowMode WRITE setSlideshowMode NOTIFY slideshowModeChanged) Q_PROPERTY(QUrl wallpaperPath READ wallpaperPath NOTIFY wallpaperPathChanged) Q_PROPERTY(QAbstractItemModel *wallpaperModel READ wallpaperModel CONSTANT) - Q_PROPERTY(QAbstractItemModel *slideshowModel READ slideshowModel CONSTANT) + Q_PROPERTY(QAbstractItemModel *slideFilterModel READ slideFilterModel CONSTANT) Q_PROPERTY(int slideTimer READ slideTimer WRITE setSlideTimer NOTIFY slideTimerChanged) Q_PROPERTY(QStringList usersWallpapers READ usersWallpapers WRITE setUsersWallpapers NOTIFY usersWallpapersChanged) Q_PROPERTY(QStringList slidePaths READ slidePaths WRITE setSlidePaths NOTIFY slidePathsChanged) @@ -73,6 +77,15 @@ }; Q_ENUM(RenderingMode) + enum SlideshowMode { + Random, + Alphabetical, + AlphabeticalReversed, + Modified, + ModifiedReversed + }; + Q_ENUM(SlideshowMode) + explicit Image(QObject* parent = nullptr); ~Image() override; @@ -97,13 +110,16 @@ RenderingMode renderingMode() const; void setRenderingMode(RenderingMode mode); + SlideshowMode slideshowMode() const; + void setSlideshowMode(SlideshowMode mode); + QSize targetSize() const; void setTargetSize(const QSize &size); KPackage::Package *package(); QAbstractItemModel* wallpaperModel(); - QAbstractItemModel* slideshowModel(); + QAbstractItemModel* slideFilterModel(); int slideTimer() const; void setSlideTimer(int time); @@ -133,6 +149,7 @@ void settingsChanged(bool); void wallpaperPathChanged(); void renderingModeChanged(); + void slideshowModeChanged(); void targetSizeChanged(); void slideTimerChanged(); void usersWallpapersChanged(); @@ -161,15 +178,14 @@ void pathCreated(const QString &path); void pathDeleted(const QString &path); void pathDirty(const QString &path); - void backgroundsFound(const QStringList &paths, const QString &token); + void backgroundsFound(); protected: void syncWallpaperPackage(); void setSingleImage(); void useSingleImageDefaults(); private: - bool m_ready; int m_delay; QStringList m_dirs; @@ -181,15 +197,16 @@ QSize m_targetSize; RenderingMode m_mode; + SlideshowMode m_slideshowMode; + KPackage::Package m_wallpaperPackage; - QStringList m_slideshowBackgrounds; - QStringList m_unseenSlideshowBackgrounds; QStringList m_slidePaths; QStringList m_uncheckedSlides; QTimer m_timer; int m_currentSlide; BackgroundListModel *m_model; SlideModel* m_slideshowModel; + SlideFilterModel* m_slideFilterModel; QFileDialog *m_dialog; QString m_img; QDateTime m_previousModified; diff --git a/wallpapers/image/image.cpp b/wallpapers/image/image.cpp --- a/wallpapers/image/image.cpp +++ b/wallpapers/image/image.cpp @@ -5,6 +5,7 @@ * Copyright 2008 Alexis Ménard * * Copyright 2014 Sebastian Kügler * * Copyright 2015 Kai Uwe Broulik * + * Copyright 2019 David Redondo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -54,6 +55,7 @@ #include #include "backgroundlistmodel.h" #include "slidemodel.h" +#include "slidefiltermodel.h" #include @@ -63,9 +65,11 @@ m_delay(10), m_dirWatch(new KDirWatch(this)), m_mode(SingleImage), + m_slideshowMode(Random), m_currentSlide(-1), m_model(nullptr), - m_slideshowModel(nullptr), + m_slideshowModel(new SlideModel(this, this)), + m_slideFilterModel(new SlideFilterModel(this)), m_dialog(nullptr) { m_wallpaperPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); @@ -77,7 +81,11 @@ connect(m_dirWatch, &KDirWatch::deleted, this, &Image::pathDeleted); m_dirWatch->startScan(); + m_slideFilterModel->setSourceModel(m_slideshowModel); + connect(this, &Image::uncheckedSlidesChanged, m_slideFilterModel, &SlideFilterModel::invalidateFilter); + useSingleImageDefaults(); + } Image::~Image() @@ -156,6 +164,25 @@ } } +Image::SlideshowMode Image::slideshowMode() const +{ + return m_slideshowMode; +} + +void Image::setSlideshowMode(Image::SlideshowMode mode) +{ + if (mode == m_slideshowMode) { + return; + } + m_slideshowMode = mode; + m_slideFilterModel->setSortingMode(mode); + m_slideFilterModel->sort(0); + if (m_mode == SlideShow) { + startSlideshow(); + } + emit slideshowModeChanged(); +} + float distance(const QSize& size, const QSize& desired) { // compute difference of areas @@ -259,7 +286,7 @@ m_wallpaper = theme.wallpaperPath(); int index = m_wallpaper.indexOf(QString::fromLatin1("/contents/images/")); if (index > -1) { // We have file from package -> get path to package - m_wallpaper = m_wallpaper.left(index); + m_wallpaper.truncate(index); } } @@ -277,15 +304,9 @@ return m_model; } -QAbstractItemModel* Image::slideshowModel() -{ - if (!m_slideshowModel) { - m_slideshowModel = new SlideModel(this, this); - m_slideshowModel->reload(m_slidePaths); - } - return m_slideshowModel; +QAbstractItemModel * Image::slideFilterModel() { + return m_slideFilterModel; } - int Image::slideTimer() const { return m_delay; @@ -546,8 +567,7 @@ } else { if (m_mode != SingleImage) { // it's a slide show, add it to the slide show - m_slideshowBackgrounds.append(path); - m_unseenSlideshowBackgrounds.append(path); + m_slideshowModel->addBackground(path); } // always add it to the user papers, though addUsersWallpaper(path); @@ -576,61 +596,49 @@ m_wallpaper = path; setSingleImage(); } else { - m_slideshowBackgrounds.append(path); - m_unseenSlideshowBackgrounds.clear(); - m_currentSlide = m_slideshowBackgrounds.size() - 2; + m_wallpaper = path; + m_slideshowModel->addBackground(path); + m_currentSlide = m_slideFilterModel->indexOf(path) - 1; nextSlide(); } //addUsersWallpaper(path); } void Image::startSlideshow() { - if (!m_ready) { + if (!m_ready || m_slideFilterModel->property("usedInConfig").toBool()) { return; } - - if(m_findToken.isEmpty()) { - // populate background list - m_timer.stop(); - m_slideshowBackgrounds.clear(); - m_unseenSlideshowBackgrounds.clear(); - BackgroundFinder *finder = new BackgroundFinder(this, m_dirs); - m_findToken = finder->token(); - connect(finder, &BackgroundFinder::backgroundsFound, this, &Image::backgroundsFound); - finder->start(); - //TODO: what would be cool: paint on the wallpaper itself a busy widget and perhaps some text - //about loading wallpaper slideshow while the thread runs - } else { - m_scanDirty = true; - } + // populate background list + m_timer.stop(); + m_slideshowModel->reload(m_slidePaths); + connect(m_slideshowModel, &SlideModel::done, this, &Image::backgroundsFound); + //TODO: what would be cool: paint on the wallpaper itself a busy widget and perhaps some text + //about loading wallpaper slideshow while the thread runs } -void Image::backgroundsFound(const QStringList &paths, const QString &token) +void Image::backgroundsFound() { - if (token != m_findToken) { - return; - } - - m_findToken.clear(); + disconnect(m_slideshowModel, &SlideModel::done, this, 0); if(m_scanDirty) { m_scanDirty = false; startSlideshow(); return; } - m_slideshowBackgrounds = paths; - for(const QString &slide : qAsConst(m_uncheckedSlides)) { - m_slideshowBackgrounds.removeAll(QUrl(slide).path()); - } - m_unseenSlideshowBackgrounds.clear(); + // start slideshow - if (m_slideshowBackgrounds.isEmpty()) { + if (m_slideFilterModel->rowCount() == 0) { // no image has been found, which is quite weird... try again later (this is useful for events which // are not detected by KDirWatch, like a NFS directory being mounted) QTimer::singleShot(1000, this, &Image::startSlideshow); } else { - m_currentSlide = -1; + if (m_currentSlide == -1 && m_slideshowMode != Random) { + m_currentSlide = m_slideFilterModel->indexOf(m_wallpaper) - 1; + } else { + m_currentSlide = -1; + } + m_slideFilterModel->sort(0); nextSlide(); m_timer.start(m_delay * 1000); } @@ -668,15 +676,6 @@ if(m_wallpaper.indexOf(QDir::homePath()) > -1){ baseUrl = QUrl(m_wallpaper); } - /* - m_dialog = new KFileDialog(baseUrl, QString::fromLatin1("*.png *.jpeg *.jpg *.xcf *.svg *.svgz *.bmp"), 0); - m_dialog->setOperationMode(KFileDialog::Opening); - m_dialog->setInlinePreviewShown(true); - m_dialog->setModal(false); - - connect(m_dialog, SIGNAL(okClicked()), this, SLOT(wallpaperBrowseCompleted())); - connect(m_dialog, SIGNAL(destroyed(QObject*)), this, SLOT(fileDialogFinished())); - */ QString path; const QStringList &locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); @@ -759,46 +758,33 @@ void Image::nextSlide() { - if (!m_ready || m_slideshowBackgrounds.isEmpty()) { + if (!m_ready || m_slideFilterModel->rowCount() == 0) { return; } - - QString previousPath; - if (m_currentSlide > -1 && m_currentSlide < m_unseenSlideshowBackgrounds.size()) { - previousPath = m_unseenSlideshowBackgrounds.takeAt(m_currentSlide); + int previousSlide = m_currentSlide; + QUrl previousPath = m_slideFilterModel->index(m_currentSlide, 0).data(BackgroundListModel::PathRole).toUrl(); + if (m_currentSlide == m_slideFilterModel->rowCount() - 1 || m_currentSlide < 0) { + m_currentSlide = 0; + } else { + m_currentSlide += 1; } - - if (m_unseenSlideshowBackgrounds.isEmpty()) { - m_unseenSlideshowBackgrounds = m_slideshowBackgrounds; - - // We're filling the queue again, make sure we can't pick up again - // the last one picked from the previous set - if (!previousPath.isEmpty()) { - m_unseenSlideshowBackgrounds.removeAll(previousPath); - - // prevent empty list - if (m_unseenSlideshowBackgrounds.isEmpty()) { - m_unseenSlideshowBackgrounds = m_slideshowBackgrounds; - } - } + //We are starting again - avoid having the same random order when we restart the slideshow + if (m_slideshowMode == Random && m_currentSlide == 0) { + m_slideFilterModel->invalidate(); + } + QUrl next = m_slideFilterModel->index(m_currentSlide, 0).data(BackgroundListModel::PathRole).toUrl(); + // And avoid showing the same picture twice + if (previousSlide == m_slideFilterModel->rowCount() - 1 && previousPath == next && m_slideFilterModel->rowCount() > 1) { + m_currentSlide += 1; + next = m_slideFilterModel->index(m_currentSlide, 0).data(BackgroundListModel::PathRole).toUrl(); } - - m_currentSlide = KRandom::random() % m_unseenSlideshowBackgrounds.size(); - const QString currentPath = m_unseenSlideshowBackgrounds.at(m_currentSlide); - - m_wallpaperPackage.setPath(currentPath); - findPreferedImageInPackage(m_wallpaperPackage); - m_timer.stop(); m_timer.start(m_delay * 1000); - - QString current = m_wallpaperPackage.filePath("preferred"); - if (current.isEmpty()) { - m_wallpaperPath = currentPath; + if (next.isEmpty()) { + m_wallpaperPath = previousPath.toLocalFile(); } else { - m_wallpaperPath = current; + m_wallpaperPath = next.toLocalFile(); } - Q_EMIT wallpaperPathChanged(); } @@ -816,22 +802,21 @@ void Image::pathCreated(const QString &path) { - if(!m_slideshowBackgrounds.contains(path)) { + if(m_slideshowModel->indexOf(path) == -1) { QFileInfo fileInfo(path); if(fileInfo.isFile() && BackgroundFinder::isAcceptableSuffix(fileInfo.suffix())) { - m_slideshowBackgrounds.append(path); - m_unseenSlideshowBackgrounds.append(path); - if(m_slideshowBackgrounds.count() == 1) { + m_slideshowModel->addBackground(path); + if(m_slideFilterModel->rowCount() == 1) { nextSlide(); } } } } void Image::pathDeleted(const QString &path) { - if(m_slideshowBackgrounds.removeAll(path)) { - m_unseenSlideshowBackgrounds.removeAll(path); + if(m_slideshowModel->indexOf(path) != -1) { + m_slideshowModel->removeBackground(path); if(path == m_img) { nextSlide(); } diff --git a/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml b/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml --- a/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml +++ b/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml @@ -30,7 +30,7 @@ id: wallpaperDelegate property alias color: backgroundRect.color - property bool selected: (wallpapersGrid.currentIndex === index) + readonly property bool selected: (view.currentIndex === index) opacity: model.pendingDeletion ? 0.5 : 1 text: model.display @@ -58,7 +58,7 @@ onTriggered: { imageModel.setPendingDeletion(index, true); if (wallpapersGrid.currentIndex === index) { - wallpapersGrid.currentIndex = (index + 1) % wallpapersGrid.count; + wallpapersGrid.currentIndex = (index + 1) % wallpapersGrid.rowCount(); } } } @@ -126,7 +126,9 @@ } onClicked: { - cfg_Image = model.path; - wallpapersGrid.forceActiveFocus(); + if (configDialog.currentWallpaper == "org.kde.image") { + cfg_Image = model.path; + } + view.currentIndex = index; } } diff --git a/wallpapers/image/imagepackage/contents/ui/config.qml b/wallpapers/image/imagepackage/contents/ui/config.qml --- a/wallpapers/image/imagepackage/contents/ui/config.qml +++ b/wallpapers/image/imagepackage/contents/ui/config.qml @@ -1,6 +1,7 @@ /* * Copyright 2013 Marco Martin * Copyright 2014 Kai Uwe Broulik + * Copyright 2019 David Redondo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,6 +35,7 @@ property alias cfg_Color: colorButton.color property string cfg_Image property int cfg_FillMode + property int cfg_SlideshowMode property alias cfg_Blur: blurRadioButton.checked property var cfg_SlidePaths: "" property int cfg_SlideInterval: 0 @@ -58,6 +60,11 @@ } onSlidePathsChanged: cfg_SlidePaths = slidePaths onUncheckedSlidesChanged: cfg_UncheckedSlides = uncheckedSlides + onSlideshowModeChanged: cfg_SlideshowMode = slideshowMode + } + + onCfg_FillModeChanged: { + resizeComboBox.setMethod() } onCfg_SlidePathsChanged: { @@ -67,6 +74,10 @@ imageWallpaper.uncheckedSlides = cfg_UncheckedSlides } + onCfg_SlideshowModeChanged: { + imageWallpaper.slideshowMode = cfg_SlideshowMode + } + property int hoursIntervalValue: Math.floor(cfg_SlideInterval / 3600) property int minutesIntervalValue: Math.floor(cfg_SlideInterval % 3600) / 60 property int secondsIntervalValue: cfg_SlideInterval % 3600 % 60 @@ -107,15 +118,55 @@ function setMethod() { for (var i = 0; i < model.length; i++) { - if (model[i]["fillMode"] === wallpaper.configuration.FillMode) { + if (model[i]["fillMode"] === root.cfg_FillMode) { resizeComboBox.currentIndex = i; var tl = model[i]["label"].length; //resizeComboBox.textLength = Math.max(resizeComboBox.textLength, tl+5); } } } } + QtControls2.ComboBox { + id: slideshowComboBox + visible: configDialog.currentWallpaper == "org.kde.slideshow" + Kirigami.FormData.label: i18nd("plasma_wallpaper_org.kde.image", "Order:") + model: [ + { + 'label': i18nd("plasma_wallpaper_org.kde.image", "Random"), + 'slideshowMode': Wallpaper.Image.Random + }, + { + 'label': i18nd("plasma_wallpaper_org.kde.image", "A to Z"), + 'slideshowMode': Wallpaper.Image.Alphabetical + }, + { + 'label': i18nd("plasma_wallpaper_org.kde.image", "Z to A"), + 'slideshowMode': Wallpaper.Image.AlphabeticalReversed + }, + { + 'label': i18nd("plasma_wallpaper_org.kde.image", "Date modified (newest first)"), + 'slideshowMode': Wallpaper.Image.ModifiedReversed + }, + { + 'label': i18nd("plasma_wallpaper_org.kde.image", "Date modified (oldest first)"), + 'slideshowMode': Wallpaper.Image.Modified + } + ] + textRole: "label" + onCurrentIndexChanged: { + cfg_SlideshowMode = model[currentIndex]["slideshowMode"]; + } + Component.onCompleted: setMethod(); + function setMethod() { + for (var i = 0; i < model.length; i++) { + if (model[i]["slideshowMode"] === wallpaper.configuration.SlideshowMode) { + slideshowComboBox.currentIndex = i; + } + } + } + } + QtControls2.ButtonGroup { id: backgroundGroup } QtControls2.RadioButton { @@ -163,31 +214,43 @@ to: 24 editable: true onValueChanged: cfg_SlideInterval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value - } - QtControls2.Label { - text: i18nd("plasma_wallpaper_org.kde.image","Hours") + + textFromValue: function(value, locale) { + return i18ndp("plasma_wallpaper_org.kde.image","%1 hour", "%1 hours", value) + } + valueFromText: function(text, locale) { + return parseInt(text); + } } QtControls2.SpinBox { id: minutesInterval value: root.minutesIntervalValue from: 0 to: 60 editable: true onValueChanged: cfg_SlideInterval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value - } - QtControls2.Label { - text: i18nd("plasma_wallpaper_org.kde.image","Minutes") + + textFromValue: function(value, locale) { + return i18ndp("plasma_wallpaper_org.kde.image","%1 minute", "%1 minutes", value) + } + valueFromText: function(text, locale) { + return parseInt(text); + } } QtControls2.SpinBox { id: secondsInterval value: root.secondsIntervalValue from: root.hoursIntervalValue === 0 && root.minutesIntervalValue === 0 ? 1 : 0 to: 60 editable: true onValueChanged: cfg_SlideInterval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value - } - QtControls2.Label { - text: i18nd("plasma_wallpaper_org.kde.image","Seconds") + + textFromValue: function(value, locale) { + return i18ndp("plasma_wallpaper_org.kde.image","%1 second", "%1 seconds", value) + } + valueFromText: function(text, locale) { + return parseInt(text); + } } } } @@ -223,16 +286,49 @@ onTriggered: imageWallpaper.openFolder(modelData) } ] - QtControls2.Label { - text: modelData.endsWith("/") ? modelData.split('/').reverse()[1] : modelData.split('/').pop() - Layout.fillWidth: true + ColumnLayout { + + width: slidePathsView.width + QtControls2.ToolTip.text: modelData - QtControls2.ToolTip.visible: folderDelegate.hovered + QtControls2.ToolTip.visible: folderDelegate.hovered && subtitle.truncated QtControls2.ToolTip.delay: 1000 QtControls2.ToolTip.timeout: 5000 + + // Header: the folder + QtControls2.Label { + Layout.fillWidth: true + elide: Text.ElideRight + text: { + var strippedPath = modelData.replace(/\/+$/, ""); + return strippedPath.split('/').pop() + } + } + // Subtitle: the path to the folder + QtControls2.Label { + id: subtitle + Layout.fillWidth: true + elide: Text.ElideRight + text: { + var strippedPath = modelData.replace(/\/+$/, ""); + return strippedPath.replace(/\/[^\/]*$/, '');; + } + font.pointSize: theme.smallestFont.pointSize + opacity: 0.6 + } } - width: slidePathsView.width - height: paintedHeight; + } + + Kirigami.Heading { + anchors.fill: parent + anchors.margins: Kirigami.Units.largeSpacing + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + visible: slidePathsView.count === 0 + level: 2 + text: i18nd("plasma_wallpaper_org.kde.image", "There are no wallpaper locations configured") + opacity: 0.3 } } } @@ -264,15 +360,48 @@ KCM.GridView { id: wallpapersGrid anchors.fill: parent - property var imageModel: (configDialog.currentWallpaper == "org.kde.image")? imageWallpaper.wallpaperModel : imageWallpaper.slideshowModel - //that min is needed as the module will be populated in an async way - //and only on demand so we can't ensure it already exists - view.currentIndex: Math.min(imageModel.indexOf(cfg_Image), imageModel.count-1) + property var imageModel: (configDialog.currentWallpaper == "org.kde.image")? imageWallpaper.wallpaperModel : imageWallpaper.slideFilterModel + + function resetCurrentIndex() { + //that min is needed as the module will be populated in an async way + //and only on demand so we can't ensure it already exists + view.currentIndex = Math.min(imageModel.indexOf(cfg_Image), imageModel.rowCount()-1) + } + + Connections { + target: imageModel + onRowsInserted: resetCurrentIndex() + onRowsRemoved: resetCurrentIndex() + } + + Connections { + target: root + onCfg_ImageChanged: resetCurrentIndex() + } + //kill the space for label under thumbnails view.model: imageModel + Component.onCompleted: { + imageModel.usedInConfig = true; + resetCurrentIndex() + } view.delegate: WallpaperDelegate { color: cfg_Color } + + Kirigami.Heading { + anchors.fill: parent + anchors.margins: Kirigami.Units.largeSpacing + // FIXME: this is needed to vertically center it in the grid for some reason + anchors.topMargin: wallpapersGrid.height + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + visible: wallpapersGrid.view.count === 0 + level: 2 + text: i18nd("plasma_wallpaper_org.kde.image", "There are no wallpapers in this slideshow") + opacity: 0.3 + } } } diff --git a/wallpapers/image/imagepackage/contents/ui/main.qml b/wallpapers/image/imagepackage/contents/ui/main.qml --- a/wallpapers/image/imagepackage/contents/ui/main.qml +++ b/wallpapers/image/imagepackage/contents/ui/main.qml @@ -52,9 +52,13 @@ //private onConfiguredImageChanged: { - imageWallpaper.addUrl(configuredImage) + if (modelImage != configuredImage && configuredImage != "") { + imageWallpaper.addUrl(configuredImage); + } } Component.onCompleted: { + wallpaper.loading = true; // delays ksplash until the wallpaper has been loaded + if (wallpaper.pluginName === "org.kde.slideshow") { wallpaper.setAction("open", i18nd("plasma_wallpaper_org.kde.image", "Open Wallpaper Image"), "document-open"); wallpaper.setAction("next", i18nd("plasma_wallpaper_org.kde.image", "Next Wallpaper Image"), "user-desktop"); @@ -68,11 +72,15 @@ targetSize: root.sourceSize slidePaths: wallpaper.configuration.SlidePaths slideTimer: wallpaper.configuration.SlideInterval + slideshowMode: wallpaper.configuration.SlideshowMode uncheckedSlides: wallpaper.configuration.UncheckedSlides } onFillModeChanged: Qt.callLater(loadImage); - onModelImageChanged: Qt.callLater(loadImage); + onModelImageChanged:{ + Qt.callLater(loadImage); + wallpaper.configuration.Image = modelImage; + } onConfigColorChanged: Qt.callLater(loadImage); onBlurChanged: Qt.callLater(loadImage); onWidthChanged: Qt.callLater(loadImage); @@ -92,6 +100,8 @@ root.replace(pendingImage, {}, isFirst ? QQC2.StackView.Immediate : QQC2.StackView.Transition);//dont' animate first show pendingImage.statusChanged.disconnect(replaceWhenLoaded); + + wallpaper.loading = false; } } pendingImage.statusChanged.connect(replaceWhenLoaded); diff --git a/wallpapers/image/imagepackage/metadata.desktop b/wallpapers/image/imagepackage/metadata.desktop --- a/wallpapers/image/imagepackage/metadata.desktop +++ b/wallpapers/image/imagepackage/metadata.desktop @@ -3,6 +3,7 @@ Icon=preferences-desktop-wallpaper Name=Image Name[ar]=صورة +Name[ast]=Imaxe Name[be@latin]=Vyjava Name[bg]=Изображение Name[bn]=ছবি @@ -33,7 +34,7 @@ Name[hr]=Slika Name[hu]=Kép Name[ia]=Image -Name[id]=Image +Name[id]=Citra Name[is]=Mynd Name[it]=Immagine Name[ja]=画像 @@ -43,7 +44,7 @@ Name[kn]=ಬಿಂಬ (ಇಮೇಜ್) Name[ko]=그림 Name[ku]=Wêne -Name[lt]=Paveikslėlis +Name[lt]=Paveikslas Name[lv]=Attēls Name[mai]=चित्र Name[mk]=Слика @@ -87,14 +88,16 @@ Comment[el]=Εμφάνιση ταπετσαρίας για τις εικόνες Comment[en_GB]=Wallpaper view for images Comment[es]=Visor de imágenes para el fondo del escritorio +Comment[et]=Pildid taustapildina Comment[eu]=Irudientzako horma-paper ikuspegia Comment[fi]=Kuvatausta Comment[fr]=Affichage d'images en fond d'écran Comment[gl]=Vista de fondo de pantalla para imaxes. Comment[hu]=Háttérkép nézet képekhez -Comment[id]=Tampilan wallpaper untuk image +Comment[id]=Tampilan wallpaper untuk citra Comment[it]=Vista delle immagini di sfondo Comment[ko]=마음에 드는 그림을 배경으로 사용 +Comment[lt]=Darbalaukio fono rodinys su paveikslais Comment[nl]=Achtergrondweergave voor afbeeldingen Comment[nn]=Bakgrunnsbiletevising for bilete Comment[pa]=ਚਿੱਤਰਾਂ ਲਈ ਵਾਲਪੇਪਰ ਝਲਕ @@ -125,7 +128,6 @@ X-KDE-PluginInfo-Name=org.kde.image X-KDE-PluginInfo-Version= X-KDE-PluginInfo-Website=https://plasma.kde.org -X-Plasma-MainScript=ui/main.qml MimeType=image/jpeg;image/png;image/svg+xml;image/svg+xml-compressed;image/bmp; X-Plasma-DropMimeTypes=image/jpeg,image/png,image/svg+xml,image/svg+xml-compressed,image/bmp diff --git a/wallpapers/image/imagepackage/platformcontents/phone/ui/config.qml b/wallpapers/image/imagepackage/platformcontents/phone/ui/config.qml --- a/wallpapers/image/imagepackage/platformcontents/phone/ui/config.qml +++ b/wallpapers/image/imagepackage/platformcontents/phone/ui/config.qml @@ -22,7 +22,7 @@ import org.kde.plasma.core 2.0 as Plasmacore import org.kde.plasma.wallpapers.image 2.0 as Wallpaper import org.kde.kquickcontrolsaddons 2.0 -import QtQuick.Controls 1.0 as QtControls +import QtQuick.Controls 2.8 as QQC2 Item { id: root @@ -41,7 +41,7 @@ //Rectangle { color: "orange"; x: formAlignment; width: formAlignment; height: 20 } - QtControls.ScrollView { + QQC2.ScrollView { anchors.fill: parent frameVisible: true @@ -71,7 +71,7 @@ } } - QtControls.Button { + QQC2.Button { anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter diff --git a/wallpapers/image/imagepackage/platformcontents/phone/ui/customwallpaper.qml b/wallpapers/image/imagepackage/platformcontents/phone/ui/customwallpaper.qml --- a/wallpapers/image/imagepackage/platformcontents/phone/ui/customwallpaper.qml +++ b/wallpapers/image/imagepackage/platformcontents/phone/ui/customwallpaper.qml @@ -22,16 +22,16 @@ import org.kde.plasma.core 2.0 as Plasmacore import org.kde.plasma.wallpapers.image 2.0 as Wallpaper import org.kde.kquickcontrolsaddons 2.0 -import QtQuick.Controls 1.0 as QtControls +import QtQuick.Controls 2.8 as QQC2 Rectangle { id: root color: syspal.window anchors.fill: parent SystemPalette {id: syspal} - QtControls.ScrollView { + QQC2.ScrollView { anchors.fill: parent frameVisible: true @@ -76,7 +76,7 @@ } } } - QtControls.Button { + QQC2.Button { anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter diff --git a/wallpapers/image/slidefiltermodel.h b/wallpapers/image/slidefiltermodel.h new file mode 100644 --- /dev/null +++ b/wallpapers/image/slidefiltermodel.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. + */ + +#ifndef SLIDEFILTERMODEL_H +#define SLIDEFILTERMODEL_H + +#include + +#include +#include + +class SlideFilterModel : public QSortFilterProxyModel { + + Q_OBJECT + + Q_PROPERTY(bool usedInConfig MEMBER m_usedInConfig NOTIFY usedInConfigChanged); + +public: + SlideFilterModel(QObject* parent); + bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + void setSourceModel(QAbstractItemModel *sourceModel) override; + void setSortingMode(Image::SlideshowMode mode); + void invalidate(); + void invalidateFilter(); + + Q_INVOKABLE int indexOf(const QString& path); + Q_INVOKABLE void openContainingFolder(int rowIndex); + +Q_SIGNALS: + void usedInConfigChanged(); + +private: + void buildRandomOrder(); + + QVector m_randomOrder; + Image::SlideshowMode m_SortingMode; + bool m_usedInConfig; +}; +#endif diff --git a/wallpapers/image/slidefiltermodel.cpp b/wallpapers/image/slidefiltermodel.cpp new file mode 100644 --- /dev/null +++ b/wallpapers/image/slidefiltermodel.cpp @@ -0,0 +1,144 @@ +/* + * Copyright 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. + */ + +#include "slidefiltermodel.h" + +#include "backgroundlistmodel.h" +#include "slidemodel.h" + +#include +#include + +#include + +SlideFilterModel::SlideFilterModel(QObject* parent) + : QSortFilterProxyModel{parent} + , m_SortingMode{Image::Random} + , m_usedInConfig{false} +{ + srand(time(nullptr)); + setSortCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); + connect(this, &SlideFilterModel::usedInConfigChanged, this, &SlideFilterModel::invalidateFilter); +} + +bool SlideFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + auto index = sourceModel()->index(source_row, 0, source_parent); + return m_usedInConfig || index.data(BackgroundListModel::ToggleRole).toBool(); +} + +void SlideFilterModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + if (this->sourceModel()) { + disconnect(this->sourceModel(), nullptr, this, nullptr); + } + QSortFilterProxyModel::setSourceModel(sourceModel); + if (m_SortingMode == Image::Random && !m_usedInConfig) { + buildRandomOrder(); + } + if(sourceModel) { + connect(sourceModel, &QAbstractItemModel::rowsInserted, this, [this] { + if (m_SortingMode != Image::Random || m_usedInConfig) { + return; + } + const int old_count = m_randomOrder.size(); + m_randomOrder.resize(this->sourceModel()->rowCount()); + std::iota(m_randomOrder.begin() + old_count, m_randomOrder.end(), old_count); + std::random_shuffle(m_randomOrder.begin() + old_count, m_randomOrder.end()); + }); + connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, [this] { + if (m_SortingMode != Image::Random || m_usedInConfig) { + return; + } + m_randomOrder.erase(std::remove_if(m_randomOrder.begin(), m_randomOrder.end(), [this] (const int v) { + return v >= this->sourceModel()->rowCount(); + }), m_randomOrder.end()); + }); + } +} + +bool SlideFilterModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const +{ + switch (m_SortingMode) { + case Image::Random: + if (m_usedInConfig) { + return source_left.row() < source_right.row(); + } + return m_randomOrder.indexOf(source_left.row()) < m_randomOrder.indexOf(source_right.row()); + case Image::Alphabetical: + return QSortFilterProxyModel::lessThan(source_left, source_right); + case Image::AlphabeticalReversed: + return !QSortFilterProxyModel::lessThan(source_left, source_right); + case Image::Modified: // oldest first + { + QFileInfo f1(source_left.data(BackgroundListModel::PathRole).toUrl().toLocalFile()); + QFileInfo f2(source_right.data(BackgroundListModel::PathRole).toUrl().toLocalFile()); + return f1.lastModified() < f2.lastModified(); + } + case Image::ModifiedReversed: // newest first + { + QFileInfo f1(source_left.data(BackgroundListModel::PathRole).toUrl().toLocalFile()); + QFileInfo f2(source_right.data(BackgroundListModel::PathRole).toUrl().toLocalFile()); + return !(f1.lastModified() < f2.lastModified()); + } + } + Q_UNREACHABLE(); +} + +void SlideFilterModel::setSortingMode(Image::SlideshowMode mode) +{ + m_SortingMode = mode; + if (m_SortingMode == Image::Random && !m_usedInConfig) { + buildRandomOrder(); + } + QSortFilterProxyModel::invalidate(); +} + +void SlideFilterModel::invalidate() +{ + if (m_SortingMode == Image::Random && !m_usedInConfig) { + std::random_shuffle(m_randomOrder.begin(), m_randomOrder.end()); + } + QSortFilterProxyModel::invalidate(); +} + +void SlideFilterModel::invalidateFilter() +{ + QSortFilterProxyModel::invalidateFilter(); +} + +int SlideFilterModel::indexOf(const QString& path) +{ + auto sourceIndex = sourceModel()->index(static_cast(sourceModel())->indexOf(path), 0); + return mapFromSource(sourceIndex).row(); +} + +void SlideFilterModel::openContainingFolder(int rowIndex) +{ + auto sourceIndex = mapToSource(index(rowIndex, 0)); + static_cast(sourceModel())->openContainingFolder(sourceIndex.row()); +} + +void SlideFilterModel::buildRandomOrder() +{ + if (sourceModel()) { + m_randomOrder.resize(sourceModel()->rowCount()); + std::iota(m_randomOrder.begin(), m_randomOrder.end(), 0); + std::random_shuffle(m_randomOrder.begin(), m_randomOrder.end()); + } +} diff --git a/wallpapers/image/slidemodel.h b/wallpapers/image/slidemodel.h --- a/wallpapers/image/slidemodel.h +++ b/wallpapers/image/slidemodel.h @@ -1,3 +1,21 @@ +/* + * Copyright 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. + */ + #ifndef SLIDEMODEL_H #define SLIDEMODEL_H @@ -13,6 +31,10 @@ void removeDir(const QString &selected); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; + +Q_SIGNALS: + void done(); + private Q_SLOTS: void removeBackgrounds(const QStringList &paths, const QString &token); void backgroundsFound(const QStringList &paths, const QString &token); diff --git a/wallpapers/image/slidemodel.cpp b/wallpapers/image/slidemodel.cpp --- a/wallpapers/image/slidemodel.cpp +++ b/wallpapers/image/slidemodel.cpp @@ -1,3 +1,21 @@ +/* + * Copyright 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. + */ + #include "slidemodel.h" void SlideModel::reload(const QStringList &selected) @@ -26,6 +44,7 @@ return; } processPaths(paths); + emit done(); } diff --git a/wallpapers/image/slideshowpackage/contents/config/main.xml b/wallpapers/image/slideshowpackage/contents/config/main.xml --- a/wallpapers/image/slideshowpackage/contents/config/main.xml +++ b/wallpapers/image/slideshowpackage/contents/config/main.xml @@ -38,6 +38,9 @@ + + + 0 + - diff --git a/wallpapers/image/slideshowpackage/metadata.desktop b/wallpapers/image/slideshowpackage/metadata.desktop --- a/wallpapers/image/slideshowpackage/metadata.desktop +++ b/wallpapers/image/slideshowpackage/metadata.desktop @@ -41,7 +41,7 @@ Name[kn]=ಚಿತ್ರಫಲಕ ಪ್ರದರ್ಶನ (ಸ್ಲೈಡ್ ಶೋ) Name[ko]=슬라이드 쇼 Name[ku]=NîşandanaSlaydê -Name[lt]=Skaidrių peržiūra +Name[lt]=Skaidrių rodymas Name[lv]=Slīdrāde Name[mai]=स्लाइडशो Name[mk]=Слајдшоу @@ -67,7 +67,6 @@ Name[sr@latin]=slajd‑šou Name[sv]=Bildspel Name[ta]=Slideshow -Name[tg]=Намоиши слайдҳо Name[th]=นำเสนอภาพนิ่ง Name[tr]=Slayt Gösterisi Name[ug]=تام تەسۋىرى @@ -85,14 +84,16 @@ Comment[el]=Παρουσίαση ταπετσαρίας Comment[en_GB]=Slideshow wallpaper Comment[es]=Fondo de escritorio de presentación +Comment[et]=Slaidiseanss taustapildina Comment[eu]=Filmina erakusketa horma-papera Comment[fi]=Diaesitystausta Comment[fr]=Diaporama en fond d'écran Comment[gl]=Fondo de pantalla de presentación. Comment[hu]=Diabemutató háttérkép Comment[id]=Wallpaper slideshow Comment[it]=Sfondo con presentazione Comment[ko]=슬라이드 쇼를 배경 그림으로 사용 +Comment[lt]=Skaidrių rodymo darbalaukio fonas Comment[nl]=Achtergrond met diavoorstelling Comment[nn]=Lysbiletvising-bakgrunn Comment[pa]=ਸਲਾਈਡ-ਸ਼ੋ ਵਾਲਪੇਪਰ @@ -124,7 +125,6 @@ X-KDE-PluginInfo-Name=org.kde.slideshow X-KDE-PluginInfo-Version= X-KDE-PluginInfo-Website=https://plasma.kde.org -X-Plasma-MainScript=ui/main.qml MimeType=image/jpeg;image/png;image/svg+xml;image/svg+xml-compressed;image/bmp; X-Plasma-DropMimeTypes=image/jpeg,image/png,image/svg+xml,image/svg+xml-compressed,image/bmp diff --git a/wallpapers/image/wallpaper.knsrc b/wallpapers/image/wallpaper.knsrc.cmake rename from wallpapers/image/wallpaper.knsrc rename to wallpapers/image/wallpaper.knsrc.cmake --- a/wallpapers/image/wallpaper.knsrc +++ b/wallpapers/image/wallpaper.knsrc.cmake @@ -1,13 +1,15 @@ [KNewStuff3] Name=Wallpapers +Name[ast]=Fondos de pantalla Name[ca]=Fons de pantalla Name[ca@valencia]=Fons de pantalla Name[cs]=Tapety Name[da]=Baggrundsbilleder Name[de]=Hintergrundbilder Name[el]=Ταπετσαρίες Name[en_GB]=Wallpapers Name[es]=Fondos del escritorio +Name[et]=Taustapildid Name[eu]=Horma-paperak Name[fi]=Taustakuvat Name[fr]=Fonds d'écran @@ -17,7 +19,8 @@ Name[id]=Wallpaper Name[it]=Sfondi Name[ko]=배경 그림 -Name[lt]=Darbalaukio paveikslėliai +Name[lt]=Darbalaukio fonai +Name[lv]=Tapetes Name[nl]=Achtergrondafbeeldingen Name[nn]=Bakgrunnsbilete Name[pa]=ਵਾਲਪੇਪਰ @@ -43,4 +46,4 @@ StandardResource=wallpaper Uncompress=archive -AdoptionCommand=qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript 'for (var i in desktops()) { d = desktops()[i]; d.wallpaperPlugin = "org.kde.image"; d.currentConfigGroup = ["Wallpaper", "org.kde.image", "General"]; d.writeConfig("Image", "%f") }' +AdoptionCommand=@QtBinariesDir@/qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript 'for (var i in desktops()) { d = desktops()[i]; d.wallpaperPlugin = "org.kde.image"; d.currentConfigGroup = ["Wallpaper", "org.kde.image", "General"]; d.writeConfig("Image", "%f") }' diff --git a/xembed-sni-proxy/fdoselectionmanager.h b/xembed-sni-proxy/fdoselectionmanager.h --- a/xembed-sni-proxy/fdoselectionmanager.h +++ b/xembed-sni-proxy/fdoselectionmanager.h @@ -39,7 +39,7 @@ ~FdoSelectionManager() override; protected: - bool nativeEventFilter(const QByteArray & eventType, void * message, long * result) override; + bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; private Q_SLOTS: void onClaimedOwnership(); @@ -51,14 +51,13 @@ bool addDamageWatch(xcb_window_t client); void dock(xcb_window_t embed_win); void undock(xcb_window_t client); - void compositingChanged(); + void setSystemTrayVisual(); uint8_t m_damageEventBase; QHash m_damageWatches; - QHash m_proxies; + QHash m_proxies; KSelectionOwner *m_selectionOwner; }; - #endif diff --git a/xembed-sni-proxy/fdoselectionmanager.cpp b/xembed-sni-proxy/fdoselectionmanager.cpp --- a/xembed-sni-proxy/fdoselectionmanager.cpp +++ b/xembed-sni-proxy/fdoselectionmanager.cpp @@ -1,6 +1,7 @@ /* * Registers as a embed container * Copyright (C) 2015 David Edmundson + * Copyright (C) 2019 Konrad Materka * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,13 +23,9 @@ #include "debug.h" #include -#include #include - -#include #include -#include #include #include @@ -66,7 +63,7 @@ xcb_connection_t *c = QX11Info::connection(); xcb_prefetch_extension_data(c, &xcb_damage_id); const auto *reply = xcb_get_extension_data(c, &xcb_damage_id); - if (reply->present) { + if (reply && reply->present) { m_damageEventBase = reply->first_event; xcb_damage_query_version_unchecked(c, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION); } else { @@ -117,24 +114,24 @@ return true; } -bool FdoSelectionManager::nativeEventFilter(const QByteArray& eventType, void* message, long int* result) +bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) { - Q_UNUSED(result); + Q_UNUSED(result) if (eventType != "xcb_generic_event_t") { return false; } - xcb_generic_event_t* ev = static_cast(message); + xcb_generic_event_t *ev = static_cast(message); const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev); if (responseType == XCB_CLIENT_MESSAGE) { const auto ce = reinterpret_cast(ev); if (ce->type == Xcb::atoms->opcodeAtom) { switch (ce->data.data32[1]) { - case SYSTEM_TRAY_REQUEST_DOCK: - dock(ce->data.data32[2]); - return true; + case SYSTEM_TRAY_REQUEST_DOCK: + dock(ce->data.data32[2]); + return true; } } } else if (responseType == XCB_UNMAP_NOTIFY) { @@ -149,11 +146,29 @@ } } else if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) { const auto damagedWId = reinterpret_cast(ev)->drawable; - const auto sniProx = m_proxies.value(damagedWId); - if(sniProx) { - sniProx->update(); + const auto sniProxy = m_proxies.value(damagedWId); + if (sniProxy) { + sniProxy->update(); xcb_damage_subtract(QX11Info::connection(), m_damageWatches[damagedWId], XCB_NONE, XCB_NONE); } + } else if (responseType == XCB_CONFIGURE_REQUEST) { + const auto event = reinterpret_cast(ev); + const auto sniProxy = m_proxies.value(event->window); + if (sniProxy) { + // The embedded window tries to move or resize. Ignore move, handle resize only. + if ((event->value_mask & XCB_CONFIG_WINDOW_WIDTH) || (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) { + sniProxy->resizeWindow(event->width, event->height); + } + } + } else if (responseType == XCB_VISIBILITY_NOTIFY) { + const auto event = reinterpret_cast(ev); + // it's possible that something showed our container window, we have to hide it + // workaround for BUG 357443: when KWin is restarted, container window is shown on top + if (event->state == XCB_VISIBILITY_UNOBSCURED) { + for (auto sniProxy : m_proxies.values()) { + sniProxy->hideContainerWindow(event->window); + } + } } return false; @@ -187,8 +202,7 @@ { qCDebug(SNIPROXY) << "Manager selection claimed"; - connect(KWindowSystem::self(), &KWindowSystem::compositingChanged, this, &FdoSelectionManager::compositingChanged); - compositingChanged(); + setSystemTrayVisual(); } void FdoSelectionManager::onFailedToClaimOwnership() @@ -200,37 +214,34 @@ void FdoSelectionManager::onLostOwnership() { qCWarning(SNIPROXY) << "lost ownership of Systray Manager"; - disconnect(KWindowSystem::self(), &KWindowSystem::compositingChanged, this, &FdoSelectionManager::compositingChanged); qApp->exit(-1); } -void FdoSelectionManager::compositingChanged() +void FdoSelectionManager::setSystemTrayVisual() { xcb_connection_t *c = QX11Info::connection(); - auto screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data; + auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data; auto trayVisual = screen->root_visual; - if (KWindowSystem::compositingActive()) { - xcb_depth_iterator_t depth_iterator = xcb_screen_allowed_depths_iterator(screen); - xcb_depth_t *depth = nullptr; + xcb_depth_iterator_t depth_iterator = xcb_screen_allowed_depths_iterator(screen); + xcb_depth_t *depth = nullptr; - while (depth_iterator.rem) { - if (depth_iterator.data->depth == 32) { - depth = depth_iterator.data; - break; - } - xcb_depth_next(&depth_iterator); + while (depth_iterator.rem) { + if (depth_iterator.data->depth == 32) { + depth = depth_iterator.data; + break; } + xcb_depth_next(&depth_iterator); + } - if (depth) { - xcb_visualtype_iterator_t visualtype_iterator = xcb_depth_visuals_iterator(depth); - while (visualtype_iterator.rem) { - xcb_visualtype_t *visualtype = visualtype_iterator.data; - if (visualtype->_class == XCB_VISUAL_CLASS_TRUE_COLOR) { - trayVisual = visualtype->visual_id; - break; - } - xcb_visualtype_next(&visualtype_iterator); + if (depth) { + xcb_visualtype_iterator_t visualtype_iterator = xcb_depth_visuals_iterator(depth); + while (visualtype_iterator.rem) { + xcb_visualtype_t *visualtype = visualtype_iterator.data; + if (visualtype->_class == XCB_VISUAL_CLASS_TRUE_COLOR) { + trayVisual = visualtype->visual_id; + break; } + xcb_visualtype_next(&visualtype_iterator); } } diff --git a/xembed-sni-proxy/sniproxy.h b/xembed-sni-proxy/sniproxy.h --- a/xembed-sni-proxy/sniproxy.h +++ b/xembed-sni-proxy/sniproxy.h @@ -1,6 +1,7 @@ /* * Holds one embedded window, registers as DBus entry * Copyright (C) 2015 David Edmundson + * Copyright (C) 2019 Konrad Materka * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -26,6 +27,7 @@ #include #include #include +#include #include #include @@ -48,6 +50,8 @@ ~SNIProxy() override; void update(); + void resizeWindow(const uint16_t width, const uint16_t height) const; + void hideContainerWindow(xcb_window_t windowId) const; /** * @return the category of the application associated to this item @@ -145,17 +149,20 @@ XTest }; + QSize calculateClientWindowSize() const; void sendClick(uint8_t mouseButton, int x, int y); QImage getImageNonComposite() const; bool isTransparentImage(const QImage &image) const; QImage convertFromNative(xcb_image_t *xcbImage) const; + QPoint calculateClickPoint() const; + void stackContainerWindow(const uint32_t stackMode) const; QDBusConnection m_dbus; xcb_window_t m_windowId; xcb_window_t m_containerWid; static int s_serviceCount; QPixmap m_pixmap; - + bool sendingClickEvent; InjectMode m_injectMode; }; diff --git a/xembed-sni-proxy/sniproxy.cpp b/xembed-sni-proxy/sniproxy.cpp --- a/xembed-sni-proxy/sniproxy.cpp +++ b/xembed-sni-proxy/sniproxy.cpp @@ -1,6 +1,7 @@ /* * Holds one embedded window, registers as DBus entry * Copyright (C) 2015 David Edmundson + * Copyright (C) 2019 Konrad Materka * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,6 +21,7 @@ #include "sniproxy.h" +#include #include #include #include @@ -49,6 +51,7 @@ #define SNI_WATCHER_PATH "/StatusNotifierWatcher" static uint16_t s_embedSize = 32; //max size of window to embed. We no longer resize the embedded window as Chromium acts stupidly. +static unsigned int XEMBED_VERSION = 0; int SNIProxy::s_serviceCount = 0; @@ -77,6 +80,7 @@ //instead lets use one DBus connection per SNI m_dbus(QDBusConnection::connectToBus(QDBusConnection::SessionBus, QStringLiteral("XembedSniProxy%1").arg(s_serviceCount++))), m_windowId(wid), + sendingClickEvent(false), m_injectMode(Direct) { //create new SNI @@ -92,17 +96,16 @@ auto c = QX11Info::connection(); - auto cookie = xcb_get_geometry(c, m_windowId); - QScopedPointer - clientGeom(xcb_get_geometry_reply(c, cookie, nullptr)); - //create a container window auto screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data; m_containerWid = xcb_generate_id(c); - uint32_t values[2]; - auto mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT; + uint32_t values[3]; + uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; values[0] = screen->black_pixel; //draw a solid background so the embedded icon doesn't get garbage in it values[1] = true; //bypass wM + values[2] = XCB_EVENT_MASK_VISIBILITY_CHANGE | // receive visibility change, to handle KWin restart #357443 + // Redirect and handle structure (size, position) requests from the embedded window. + XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; xcb_create_window (c, /* connection */ XCB_COPY_FROM_PARENT, /* depth */ m_containerWid, /* window Id */ @@ -126,8 +129,7 @@ */ #ifndef VISUAL_DEBUG - const uint32_t stackBelowData[] = {XCB_STACK_MODE_BELOW}; - xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_STACK_MODE, stackBelowData); + stackContainerWindow(XCB_STACK_MODE_BELOW); NETWinInfo wm(c, m_containerWid, screen->root, NET::Properties(), NET::Properties2()); wm.setOpacity(0); @@ -153,45 +155,16 @@ xcb_change_save_set(c, XCB_SET_MODE_INSERT, wid); //tell client we're embedding it - xembed_message_send(wid, XEMBED_EMBEDDED_NOTIFY, m_containerWid, 0, 0); + xembed_message_send(wid, XEMBED_EMBEDDED_NOTIFY, 0, m_containerWid, XEMBED_VERSION); //move window we're embedding const uint32_t windowMoveConfigVals[2] = { 0, 0 }; xcb_configure_window(c, wid, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, windowMoveConfigVals); - - QSize clientWindowSize; - - if (clientGeom) { - clientWindowSize = QSize(clientGeom->width, clientGeom->height); - } - //if the window is a clearly stupid size resize to be something sensible - //this is needed as chormium and such when resized just fill the icon with transparent space and only draw in the middle - //however spotify does need this as by default the window size is 900px wide. - //use an artbitrary heuristic to make sure icons are always sensible - if (clientWindowSize.isEmpty() || clientWindowSize.width() > s_embedSize || clientWindowSize.height() > s_embedSize ) - { - qCDebug(SNIPROXY) << "Resizing window" << wid << Title() << "from w*h" << clientWindowSize; - - xcb_configure_notify_event_t event; - memset(&event, 0x00, sizeof(xcb_configure_notify_event_t)); - event.response_type = XCB_CONFIGURE_NOTIFY; - event.event = wid; - event.window = wid; - event.width = s_embedSize; - event.height = s_embedSize; - xcb_send_event(c, false, wid, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *) &event); - - const uint32_t windowMoveConfigVals[2] = { s_embedSize, s_embedSize }; - xcb_configure_window(c, wid, - XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, - windowMoveConfigVals); - - clientWindowSize = QSize(s_embedSize, s_embedSize); - } + QSize clientWindowSize = calculateClientWindowSize(); //show the embedded window otherwise nothing happens xcb_map_window(c, wid); @@ -239,14 +212,64 @@ int h = image.height(); m_pixmap = QPixmap::fromImage(image); - if (w != s_embedSize || h != s_embedSize) { + if (w > s_embedSize || h > s_embedSize) { qCDebug(SNIPROXY) << "Scaling pixmap of window" << m_windowId << Title() << "from w*h" << w << h; m_pixmap = m_pixmap.scaled(s_embedSize, s_embedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } emit NewIcon(); emit NewToolTip(); } +void SNIProxy::resizeWindow(const uint16_t width, const uint16_t height) const +{ + auto connection = QX11Info::connection(); + + uint16_t widthNormalized = std::min(width, s_embedSize); + uint16_t heighNormalized = std::min(height, s_embedSize); + + const uint32_t windowSizeConfigVals[2] = { widthNormalized, heighNormalized }; + xcb_configure_window(connection, m_windowId, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, + windowSizeConfigVals); + + xcb_flush(connection); +} + +void SNIProxy::hideContainerWindow(xcb_window_t windowId) const +{ + if (m_containerWid == windowId && !sendingClickEvent) { + qDebug() << "Container window visible, stack below"; + stackContainerWindow(XCB_STACK_MODE_BELOW); + } +} + +QSize SNIProxy::calculateClientWindowSize() const +{ + auto c = QX11Info::connection(); + + auto cookie = xcb_get_geometry(c, m_windowId); + QScopedPointer + clientGeom(xcb_get_geometry_reply(c, cookie, nullptr)); + + QSize clientWindowSize; + if (clientGeom) { + clientWindowSize = QSize(clientGeom->width, clientGeom->height); + } + //if the window is a clearly stupid size resize to be something sensible + //this is needed as chromium and such when resized just fill the icon with transparent space and only draw in the middle + //however KeePass2 does need this as by default the window size is 273px wide and is not transparent + //use an artbitrary heuristic to make sure icons are always sensible + if (clientWindowSize.isEmpty() || clientWindowSize.width() > s_embedSize || clientWindowSize.height() > s_embedSize) { + qCDebug(SNIPROXY) << "Resizing window" << m_windowId << Title() << "from w*h" << clientWindowSize; + + resizeWindow(s_embedSize, s_embedSize); + + clientWindowSize = QSize(s_embedSize, s_embedSize); + } + + return clientWindowSize; +} + void sni_cleanup_xcb_image(void *data) { xcb_image_destroy(static_cast(data)); } @@ -277,15 +300,10 @@ QImage SNIProxy::getImageNonComposite() const { auto c = QX11Info::connection(); - auto cookie = xcb_get_geometry(c, m_windowId); - QScopedPointer - geom(xcb_get_geometry_reply(c, cookie, nullptr)); - if (!geom) { - return QImage(); - } + QSize clientWindowSize = calculateClientWindowSize(); - xcb_image_t *image = xcb_image_get(c, m_windowId, 0, 0, geom->width, geom->height, 0xFFFFFFFF, XCB_IMAGE_FORMAT_Z_PIXMAP); + xcb_image_t *image = xcb_image_get(c, m_windowId, 0, 0, clientWindowSize.width(), clientWindowSize.height(), 0xFFFFFFFF, XCB_IMAGE_FORMAT_Z_PIXMAP); // Don't hook up cleanup yet, we may use a different QImage after all QImage naiveConversion; @@ -373,6 +391,58 @@ return image; } +/* + Wine is using XWindow Shape Extension for transparent tray icons. + We need to find first clickable point starting from top-left. +*/ +QPoint SNIProxy::calculateClickPoint() const +{ + QPoint clickPoint = QPoint(0, 0); + + auto c = QX11Info::connection(); + + // request extent to check if shape has been set + xcb_shape_query_extents_cookie_t extentsCookie = xcb_shape_query_extents(c, m_windowId); + // at the same time make the request for rectangles (even if this request isn't needed) + xcb_shape_get_rectangles_cookie_t rectaglesCookie = xcb_shape_get_rectangles(c, m_windowId, XCB_SHAPE_SK_BOUNDING); + + QScopedPointer + extentsReply(xcb_shape_query_extents_reply(c, extentsCookie, nullptr)); + QScopedPointer + rectanglesReply(xcb_shape_get_rectangles_reply(c, rectaglesCookie, nullptr)); + + if (!extentsReply || !rectanglesReply || !extentsReply->bounding_shaped) { + return clickPoint; + } + + xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(rectanglesReply.get()); + if (!rectangles) { + return clickPoint; + } + + const QImage image = getImageNonComposite(); + + double minLength = sqrt(pow(image.height(), 2) + pow(image.width(), 2)); + const int nRectangles = xcb_shape_get_rectangles_rectangles_length(rectanglesReply.get()); + for (int i = 0; i < nRectangles; ++i) { + double length = sqrt(pow(rectangles[i].x, 2) + pow(rectangles[i].y, 2)); + if (length < minLength) { + minLength = length; + clickPoint = QPoint(rectangles[i].x, rectangles[i].y); + } + } + + qCDebug(SNIPROXY) << "Click point:" << clickPoint; + return clickPoint; +} + +void SNIProxy::stackContainerWindow(const uint32_t stackMode) const +{ + auto c = QX11Info::connection(); + const uint32_t stackData[] = {stackMode}; + xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_STACK_MODE, stackData); +} + //____________properties__________ QString SNIProxy::Category() const @@ -454,6 +524,7 @@ //ideally we should make this match the plasmoid hit area qCDebug(SNIPROXY) << "Received click" << mouseButton << "with passed x*y" << x << y; + sendingClickEvent = true; auto c = QX11Info::connection(); @@ -474,25 +545,25 @@ //move our window so the mouse is within its geometry uint32_t configVals[2] = {0, 0}; + const QPoint clickPoint = calculateClickPoint(); if (mouseButton >= XCB_BUTTON_INDEX_4) { //scroll event, take pointer position configVals[0] = pointer->root_x; configVals[1] = pointer->root_y; } else { if (pointer->root_x > x + clientGeom->width) configVals[0] = pointer->root_x - clientGeom->width + 1; else - configVals[0] = static_cast(x); + configVals[0] = static_cast(x - clickPoint.x()); if (pointer->root_y > y + clientGeom->height) configVals[1] = pointer->root_y - clientGeom->height + 1; else - configVals[1] = static_cast(y); + configVals[1] = static_cast(y - clickPoint.y()); } xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, configVals); //pull window up - const uint32_t stackAboveData[] = {XCB_STACK_MODE_ABOVE}; - xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_STACK_MODE, stackAboveData); + stackContainerWindow(XCB_STACK_MODE_ABOVE); //mouse down if (m_injectMode == Direct) { @@ -505,8 +576,8 @@ event->root = QX11Info::appRootWindow(); event->root_x = x; event->root_y = y; - event->event_x = 0; - event->event_y = 0; + event->event_x = static_cast(clickPoint.x()); + event->event_y = static_cast(clickPoint.y()); event->child = 0; event->state = 0; event->detail = mouseButton; @@ -529,8 +600,8 @@ event->root = QX11Info::appRootWindow(); event->root_x = x; event->root_y = y; - event->event_x = 0; - event->event_y = 0; + event->event_x = static_cast(clickPoint.x()); + event->event_y = static_cast(clickPoint.y()); event->child = 0; event->state = 0; event->detail = mouseButton; @@ -542,7 +613,8 @@ } #ifndef VISUAL_DEBUG - const uint32_t stackBelowData[] = {XCB_STACK_MODE_BELOW}; - xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_STACK_MODE, stackBelowData); + stackContainerWindow(XCB_STACK_MODE_BELOW); #endif + + sendingClickEvent = false; } diff --git a/xembed-sni-proxy/xembedsniproxy.desktop b/xembed-sni-proxy/xembedsniproxy.desktop --- a/xembed-sni-proxy/xembedsniproxy.desktop +++ b/xembed-sni-proxy/xembedsniproxy.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Exec=xembedsniproxy Name=XembedSniProxy +Name[ast]=XembedSniProxy Name[ca]=XembedSniProxy Name[ca@valencia]=XembedSniProxy Name[cs]=XembedSniProxy